@tabnas/transduce 0.1.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 (63) hide show
  1. package/LICENSE +21 -0
  2. package/dist/capability.d.ts +6 -0
  3. package/dist/capability.js +52 -0
  4. package/dist/capability.js.map +1 -0
  5. package/dist/datum.d.ts +64 -0
  6. package/dist/datum.js +391 -0
  7. package/dist/datum.js.map +1 -0
  8. package/dist/error.d.ts +43 -0
  9. package/dist/error.js +169 -0
  10. package/dist/error.js.map +1 -0
  11. package/dist/event.d.ts +56 -0
  12. package/dist/event.js +101 -0
  13. package/dist/event.js.map +1 -0
  14. package/dist/guard.d.ts +20 -0
  15. package/dist/guard.js +90 -0
  16. package/dist/guard.js.map +1 -0
  17. package/dist/json.d.ts +4 -0
  18. package/dist/json.js +92 -0
  19. package/dist/json.js.map +1 -0
  20. package/dist/limits.d.ts +36 -0
  21. package/dist/limits.js +133 -0
  22. package/dist/limits.js.map +1 -0
  23. package/dist/lines.d.ts +39 -0
  24. package/dist/lines.js +1139 -0
  25. package/dist/lines.js.map +1 -0
  26. package/dist/matcher.d.ts +26 -0
  27. package/dist/matcher.js +245 -0
  28. package/dist/matcher.js.map +1 -0
  29. package/dist/parser-source.d.ts +27 -0
  30. package/dist/parser-source.js +194 -0
  31. package/dist/parser-source.js.map +1 -0
  32. package/dist/route.d.ts +61 -0
  33. package/dist/route.js +197 -0
  34. package/dist/route.js.map +1 -0
  35. package/dist/rule-events.d.ts +58 -0
  36. package/dist/rule-events.js +598 -0
  37. package/dist/rule-events.js.map +1 -0
  38. package/dist/scan.d.ts +19 -0
  39. package/dist/scan.js +70 -0
  40. package/dist/scan.js.map +1 -0
  41. package/dist/selector.d.ts +40 -0
  42. package/dist/selector.js +154 -0
  43. package/dist/selector.js.map +1 -0
  44. package/dist/sink.d.ts +29 -0
  45. package/dist/sink.js +184 -0
  46. package/dist/sink.js.map +1 -0
  47. package/dist/source.d.ts +37 -0
  48. package/dist/source.js +113 -0
  49. package/dist/source.js.map +1 -0
  50. package/dist/table-from-json.d.ts +12 -0
  51. package/dist/table-from-json.js +217 -0
  52. package/dist/table-from-json.js.map +1 -0
  53. package/dist/table.d.ts +81 -0
  54. package/dist/table.js +141 -0
  55. package/dist/table.js.map +1 -0
  56. package/dist/transduce.d.ts +28 -0
  57. package/dist/transduce.js +109 -0
  58. package/dist/transduce.js.map +1 -0
  59. package/dist/tsconfig.tsbuildinfo +1 -0
  60. package/dist/value.d.ts +16 -0
  61. package/dist/value.js +96 -0
  62. package/dist/value.js.map +1 -0
  63. package/package.json +65 -0
package/dist/lines.js ADDED
@@ -0,0 +1,1139 @@
1
+ "use strict";
2
+ /* Copyright (c) 2026 tabnas, MIT License */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.LinesSource = exports.LineFormat = exports.DEFAULT_CHUNK_BYTES = void 0;
5
+ // `LinesSource`: JSON Lines and CSV a record (or a chunk of records) at a
6
+ // time, from a synchronous iterable of chunks or pushed chunk by chunk.
7
+ //
8
+ // The engine parses a whole string, so a document is retained at least
9
+ // once however it is consumed. Line-delimited formats do not need to be one
10
+ // document: JSON Lines is one value per line, and CSV is a header plus
11
+ // independent records, so each can be parsed a piece at a time with one
12
+ // reused parser and the memory a run needs stops depending on the input's
13
+ // size. Its events are exactly the whole-input parse's (the array of the
14
+ // per-line values, or of the records).
15
+ //
16
+ // Both formats cut the input where the grammar ends a record, reading the
17
+ // text as the grammar's own lexer does (with the line characters, quotes,
18
+ // separators and comments its parser's resolved options set), and nothing
19
+ // read is left unparsed but a blank JSON Lines record, which the grammar
20
+ // skips too.
21
+ //
22
+ // JSON Lines: each record is parsed with one `@tabnas/json` parser. A
23
+ // record is the text between line characters outside a string, so a lone
24
+ // `\r` ends one as `\n` does. A record of nothing but the grammar's spaces
25
+ // (a space or a tab) is blank and skipped; any other is parsed, so a line
26
+ // holding a form feed or a no-break space fails as the grammar fails it.
27
+ // On the incremental path (`runIncremental`, or a writer asked for it) the
28
+ // record goes through the rule-event adapter, reset per record, so numbers
29
+ // keep their lexemes; on the walking path (`run`) the value is walked and
30
+ // numbers carry none. A record that does not parse is `INPUT_INVALID` with
31
+ // its line's number as the row.
32
+ //
33
+ // CSV: the input is cut into chunks of whole records. A quote opens a
34
+ // quoted field only where the lexer starts a token (a record's start, after
35
+ // the field separator or a space), so a quote inside a field is that
36
+ // field's text; the configured quote reads `""` as one quote; a quoted
37
+ // field, a backtick string and a block comment may span lines; and a line
38
+ // character anywhere else, a lone `\r` as much as `\n`, ends a record. The
39
+ // header record, the first record the grammar reads (a blank or comment
40
+ // line before it is none), is kept and prepended to every chunk after the
41
+ // first when `header` is on, so the reused `@tabnas/csv` parser names each
42
+ // record's fields as the whole input would. A chunk closes at the first
43
+ // record boundary at or past `DEFAULT_CHUNK_BYTES` (or the configured
44
+ // size), so one chunk, and never a fraction of a record, is what a parse
45
+ // holds. An input that ends inside a quoted field fails with the grammar's
46
+ // `unterminated_string`, in the header as anywhere. CSV is walked on both
47
+ // paths.
48
+ //
49
+ // The input is read a piece at a time, and a piece ends just past each of
50
+ // the grammar's line endings: one of its line characters (a lone `\r` or a
51
+ // configured separator as much as `\n`), a `\r\n`, or under `record.empty`
52
+ // the whole line token the lexer reads. A line character inside one of the
53
+ // grammar's fixed tokens (a field separator such as `"\n~"`) is the token's
54
+ // and ends no piece. So a record never waits for a `\n` to end, and a chunk
55
+ // can close after any record, however many of them one `\n`-terminated line
56
+ // holds. Where the input so far ends inside what may be one line ending,
57
+ // the piece waits for the next chunk of it.
58
+ //
59
+ // Every count is in UTF-8 bytes of the input as it arrives. A single
60
+ // record larger than `max_record_bytes` (a record with its line ending; for
61
+ // the CSV header, everything up to its end) fails with that limit's name
62
+ // the moment it passes the limit, whatever its length, rather than growing
63
+ // a chunk without bound.
64
+ const csv_1 = require("@tabnas/csv");
65
+ const json_1 = require("@tabnas/json");
66
+ const error_1 = require("./error");
67
+ const event_1 = require("./event");
68
+ const guard_1 = require("./guard");
69
+ const limits_1 = require("./limits");
70
+ const rule_events_1 = require("./rule-events");
71
+ const source_1 = require("./source");
72
+ // How much of the input one CSV parse holds, at most one record over.
73
+ exports.DEFAULT_CHUNK_BYTES = 256 * 1024;
74
+ exports.LineFormat = Object.freeze({
75
+ jsonl() {
76
+ return { type: 'jsonl' };
77
+ },
78
+ // CSV with a header line and the grammar's default options, or the given
79
+ // ones.
80
+ csv(header = true, options = {}) {
81
+ return { type: 'csv', header, options };
82
+ },
83
+ });
84
+ // A line-delimited input as a source.
85
+ class LinesSource {
86
+ // `input` may be `null` for a source used only through `writer`.
87
+ constructor(input, format) {
88
+ this.sourceLimits = limits_1.Limits.default();
89
+ this.abortFlag = new limits_1.AbortFlag();
90
+ this.sourceMetrics = new limits_1.Metrics();
91
+ this.chunkSize = exports.DEFAULT_CHUNK_BYTES;
92
+ this.consumed = false;
93
+ this.input = input;
94
+ this.format = format;
95
+ }
96
+ limits(limits) {
97
+ this.sourceLimits = limits;
98
+ return this;
99
+ }
100
+ abort(abort) {
101
+ this.abortFlag = abort;
102
+ return this;
103
+ }
104
+ metrics(metrics) {
105
+ this.sourceMetrics = metrics;
106
+ return this;
107
+ }
108
+ // The CSV chunk size; a chunk closes at the first record boundary at or
109
+ // past it. Zero closes a chunk at every record.
110
+ chunkBytes(bytes) {
111
+ this.chunkSize = bytes;
112
+ return this;
113
+ }
114
+ // Both formats through the walk; JSON Lines numbers carry no lexeme.
115
+ run(sink) {
116
+ return this.drive(this.writer(sink, false));
117
+ }
118
+ // JSON Lines through the rule-event adapter (lexemes kept), CSV through
119
+ // the walk.
120
+ runIncremental(sink) {
121
+ return this.drive(this.writer(sink, true));
122
+ }
123
+ drive(writer) {
124
+ if (this.consumed) {
125
+ throw new error_1.Fail('STREAM_REUSED', 'a LinesSource reads its input once');
126
+ }
127
+ if (null === this.input) {
128
+ throw new TypeError('LinesSource: no input; use writer() to push chunks');
129
+ }
130
+ const input = this.input;
131
+ this.input = null;
132
+ this.consumed = true;
133
+ const chunks = 'string' === typeof input || input instanceof Uint8Array ? [input] : input;
134
+ for (const chunk of chunks) {
135
+ if ('stop' === writer.write(chunk))
136
+ return 'stop';
137
+ }
138
+ return writer.end();
139
+ }
140
+ // A writer that pushes the input into `sink`, on the incremental path
141
+ // when `incremental` is true.
142
+ writer(sink, incremental = false) {
143
+ const options = {
144
+ limits: this.sourceLimits,
145
+ abort: this.abortFlag,
146
+ metrics: this.sourceMetrics,
147
+ chunkBytes: this.chunkSize,
148
+ };
149
+ const driver = 'jsonl' === this.format.type
150
+ ? incremental
151
+ ? new JsonlIncremental(sink, options)
152
+ : new JsonlWalk(sink, options)
153
+ : new CsvWalk(sink, options, this.format.header, this.format.options);
154
+ return new Writer(driver);
155
+ }
156
+ }
157
+ exports.LinesSource = LinesSource;
158
+ // The writer's state machine: a stop or a failure ends it for good.
159
+ class Writer {
160
+ constructor(driver) {
161
+ this.started = false;
162
+ this.state = 'open';
163
+ this.failure = null;
164
+ this.driver = driver;
165
+ this.onPiece = (row, text, bytes) => this.driver.piece(row, text, bytes);
166
+ }
167
+ guard(step) {
168
+ if ('stopped' === this.state)
169
+ return 'stop';
170
+ if ('failed' === this.state)
171
+ throw this.failure;
172
+ if ('ended' === this.state)
173
+ throw error_1.Fail.protocol('the line source has already ended');
174
+ try {
175
+ if (!this.started) {
176
+ this.started = true;
177
+ if ('stop' === this.driver.begin())
178
+ return this.finish('stopped');
179
+ }
180
+ const flow = step();
181
+ if ('stop' === flow)
182
+ return this.finish('stopped');
183
+ return flow;
184
+ }
185
+ catch (err) {
186
+ this.failure = err;
187
+ this.finish('failed');
188
+ throw err;
189
+ }
190
+ }
191
+ finish(state) {
192
+ this.state = state;
193
+ this.driver.close();
194
+ return 'stop';
195
+ }
196
+ write(chunk) {
197
+ return this.guard(() => {
198
+ const bytes = 'string' === typeof chunk ? Buffer.from(chunk, 'utf8') : chunk;
199
+ return this.driver.splitter.feed(bytes, this.onPiece);
200
+ });
201
+ }
202
+ end() {
203
+ const flow = this.guard(() => {
204
+ if ('stop' === this.driver.splitter.end(this.onPiece))
205
+ return 'stop';
206
+ return this.driver.finish();
207
+ });
208
+ if ('open' === this.state) {
209
+ this.finish('ended');
210
+ return flow;
211
+ }
212
+ return flow;
213
+ }
214
+ }
215
+ const EMPTY = new Uint8Array(0);
216
+ const ENCODER = new TextEncoder();
217
+ // The characters of one of the engine's resolved character maps while it
218
+ // lexes their kind, else none.
219
+ function lexed(on, chars) {
220
+ return on && chars ? Object.keys(chars) : [];
221
+ }
222
+ // The input in pieces, each ending just past a line ending: one of the
223
+ // grammar's line characters, a `\r\n`, or under `line.single`
224
+ // (`record.empty`) the whole line token the lexer reads, every line
225
+ // character up to a repeated one. A line character inside one of the
226
+ // grammar's fixed tokens (a field separator such as `"\n~"`) is the token's
227
+ // and ends no piece, and where a fixed token holds one, a piece takes the
228
+ // whole run of line characters the lexer reads as one token, so that no
229
+ // piece starts inside a line token. A record therefore never ends inside a
230
+ // piece, only at its end. Each piece carries the row the engine gives its
231
+ // first character: one more than the row characters before it. Where the
232
+ // input so far ends inside what may be a line ending, the piece waits for
233
+ // more.
234
+ class PieceSplitter {
235
+ constructor(line, rows, single, fixed) {
236
+ // The bytes of the piece being read, and any after it not yet taken.
237
+ this.pending = EMPTY;
238
+ // How much of `pending` has been searched for a line ending.
239
+ this.scanned = 0;
240
+ // Once a line ending is found: where its token ends so far, and its
241
+ // characters.
242
+ this.tokenEnd = -1;
243
+ this.token = [];
244
+ // Where the next piece starts: its row, and its byte offset in it.
245
+ this.row = 1;
246
+ this.offset = 0;
247
+ this.ends = new Uint8Array(256);
248
+ // How many more bytes the piece being read may take, and the failure
249
+ // when it takes more, given the row it starts on: the bound is the
250
+ // record's, so the format that reads the records sets both.
251
+ this.budget = () => Infinity;
252
+ this.over = (row) => error_1.Fail.input(`line ${row} is too long`).at(row, 1);
253
+ // The engine keeps a character outside the basic plane as its two
254
+ // UTF-16 halves, each a line character; the input holds it as one
255
+ // four-byte form, which ends a piece when both halves are line
256
+ // characters.
257
+ this.line = line.filter((c) => !isHigh(c) && !isLow(c));
258
+ for (const h of line.filter(isHigh)) {
259
+ for (const l of line.filter(isLow))
260
+ this.line.push(h + l);
261
+ }
262
+ this.forms = this.line.map((c) => ENCODER.encode(c));
263
+ for (const form of this.forms)
264
+ this.ends[form[form.length - 1]] = 1;
265
+ this.single = single;
266
+ this.spanning = fixed
267
+ .filter((token) => Array.from({ length: token.length }, (_, i) => token[i]).some((c) => line.includes(c)))
268
+ .map((token) => ENCODER.encode(token));
269
+ this.rows = rows;
270
+ }
271
+ // The row the next piece starts on.
272
+ get nextRow() {
273
+ return this.row;
274
+ }
275
+ feed(bytes, piece) {
276
+ if (0 === bytes.length)
277
+ return 'continue';
278
+ this.pending = 0 === this.pending.length ? bytes : Buffer.concat([this.pending, bytes]);
279
+ return this.drain(piece, false);
280
+ }
281
+ end(piece) {
282
+ if ('stop' === this.drain(piece, true))
283
+ return 'stop';
284
+ if (0 === this.pending.length)
285
+ return 'continue';
286
+ const raw = this.pending;
287
+ this.pending = EMPTY;
288
+ this.scanned = 0;
289
+ return this.emit(raw, piece);
290
+ }
291
+ drain(piece, atEnd) {
292
+ for (;;) {
293
+ if (this.tokenEnd < 0 && !this.findEnding(atEnd)) {
294
+ if (this.pending.length > this.budget())
295
+ throw this.over(this.row);
296
+ return 'continue';
297
+ }
298
+ if (!this.followToken(atEnd))
299
+ return 'continue';
300
+ const raw = this.pending.subarray(0, this.tokenEnd);
301
+ this.pending = this.pending.subarray(this.tokenEnd);
302
+ this.scanned = 0;
303
+ this.tokenEnd = -1;
304
+ this.token = [];
305
+ if ('stop' === this.emit(raw, piece))
306
+ return 'stop';
307
+ }
308
+ }
309
+ // Finds the first line ending past `scanned`, checked against the line
310
+ // character's whole UTF-8 form, and starts its token. False while the
311
+ // input so far holds none, or cannot yet tell where one ends.
312
+ findEnding(atEnd) {
313
+ const p = this.pending;
314
+ for (let i = this.scanned; i < p.length; i++) {
315
+ if (!this.ends[p[i]])
316
+ continue;
317
+ const k = this.formEnding(i + 1);
318
+ if (k < 0)
319
+ continue;
320
+ const at = i + 1 - this.forms[k].length;
321
+ const inside = this.inFixed(at, k, atEnd);
322
+ if (true === inside)
323
+ continue;
324
+ if (null === inside) {
325
+ this.scanned = at;
326
+ return false;
327
+ }
328
+ this.tokenEnd = i + 1;
329
+ this.token = [this.line[k]];
330
+ if (this.tokenEnd > this.budget())
331
+ throw this.over(this.row);
332
+ return true;
333
+ }
334
+ this.scanned = p.length;
335
+ return false;
336
+ }
337
+ // Whether the `k`th line character, at `at`, lies inside one of the fixed
338
+ // tokens that hold a line character; null while the input so far cannot
339
+ // tell.
340
+ inFixed(at, k, atEnd) {
341
+ const p = this.pending;
342
+ const form = this.forms[k];
343
+ let unsure = false;
344
+ for (const token of this.spanning) {
345
+ for (let j = 0; j + form.length <= token.length; j++) {
346
+ if (!sameBytes(token, j, form, 0, form.length))
347
+ continue;
348
+ const start = at - j;
349
+ if (start < 0 || !sameBytes(p, start, token, 0, j))
350
+ continue;
351
+ const after = at + form.length;
352
+ const rest = token.length - j - form.length;
353
+ const have = Math.min(rest, p.length - after);
354
+ if (!sameBytes(p, after, token, j + form.length, have))
355
+ continue;
356
+ if (have === rest)
357
+ return true;
358
+ if (!atEnd)
359
+ unsure = true;
360
+ }
361
+ }
362
+ return unsure ? null : false;
363
+ }
364
+ // The line character whose UTF-8 form `pending` holds just before `end`,
365
+ // by its index, or -1.
366
+ formEnding(end) {
367
+ const p = this.pending;
368
+ next: for (let k = 0; k < this.forms.length; k++) {
369
+ const form = this.forms[k];
370
+ if (end < form.length)
371
+ continue;
372
+ for (let j = 0; j < form.length; j++) {
373
+ if (p[end - form.length + j] !== form[j])
374
+ continue next;
375
+ }
376
+ return k;
377
+ }
378
+ return -1;
379
+ }
380
+ // Takes the rest of the line token the piece ends with: under
381
+ // `line.single`, every line character not yet in it, as the lexer reads
382
+ // one; where a fixed token holds a line character, every line character,
383
+ // as the lexer reads a run; otherwise a `\n` after a `\r`, so that `\r\n`
384
+ // is one line ending and a record is held to its own line. False while
385
+ // the input so far cannot tell whether the token goes on.
386
+ followToken(atEnd) {
387
+ const p = this.pending;
388
+ for (;;) {
389
+ let next = -1;
390
+ let partial = false;
391
+ for (let k = 0; k < this.line.length && next < 0; k++) {
392
+ const c = this.line[k];
393
+ const goesOn = this.single
394
+ ? !this.token.includes(c)
395
+ : 0 < this.spanning.length || (1 === this.token.length && '\r' === this.token[0] && '\n' === c);
396
+ if (!goesOn)
397
+ continue;
398
+ const form = this.forms[k];
399
+ const have = p.length - this.tokenEnd;
400
+ let prefix = true;
401
+ for (let j = 0; j < Math.min(have, form.length); j++) {
402
+ if (p[this.tokenEnd + j] !== form[j]) {
403
+ prefix = false;
404
+ break;
405
+ }
406
+ }
407
+ if (!prefix)
408
+ continue;
409
+ if (have >= form.length)
410
+ next = k;
411
+ else
412
+ partial = true;
413
+ }
414
+ if (0 <= next) {
415
+ this.tokenEnd += this.forms[next].length;
416
+ this.token.push(this.line[next]);
417
+ if (this.tokenEnd > this.budget())
418
+ throw this.over(this.row);
419
+ continue;
420
+ }
421
+ return !partial || atEnd;
422
+ }
423
+ }
424
+ emit(raw, piece) {
425
+ const row = this.row;
426
+ const bad = utf8Invalid(raw);
427
+ if (0 <= bad) {
428
+ const valid = DECODER.decode(raw.subarray(0, bad));
429
+ const [badRow, offset] = advance(this.rows, this.row, this.offset, valid);
430
+ throw error_1.Fail.input(`line ${badRow} is not UTF-8 from its byte ${offset + 1}`).at(badRow, offset + 1);
431
+ }
432
+ const text = DECODER.decode(raw);
433
+ [this.row, this.offset] = advance(this.rows, this.row, this.offset, text);
434
+ return piece(row, text, raw.length);
435
+ }
436
+ }
437
+ // The row, and the byte offset in it, just after `text`, which starts at
438
+ // `row` and `offset`: a row character starts the next row. The engine reads
439
+ // a UTF-16 code unit at a time, so a row character outside the basic plane
440
+ // is its two halves, and counts as both.
441
+ function advance(rows, row, offset, text) {
442
+ for (let i = 0; i < text.length; i++) {
443
+ const c = text[i];
444
+ if (rows.includes(c)) {
445
+ row++;
446
+ offset = 0;
447
+ }
448
+ else {
449
+ // A character outside the basic plane is four bytes, counted at its
450
+ // first half.
451
+ const u = c.charCodeAt(0);
452
+ offset += u < 0x80 ? 1 : u < 0x800 ? 2 : isHigh(c) ? 4 : isLow(c) ? 0 : 3;
453
+ }
454
+ }
455
+ return [row, offset];
456
+ }
457
+ // Whether one UTF-16 code unit is the first or the second half of a
458
+ // character outside the basic plane.
459
+ function isHigh(c) {
460
+ const u = c.charCodeAt(0);
461
+ return 1 === c.length && 0xd800 <= u && u < 0xdc00;
462
+ }
463
+ function isLow(c) {
464
+ const u = c.charCodeAt(0);
465
+ return 1 === c.length && 0xdc00 <= u && u < 0xe000;
466
+ }
467
+ // Whether `n` bytes of `a` from `ai` are those of `b` from `bi`.
468
+ function sameBytes(a, ai, b, bi, n) {
469
+ if (ai + n > a.length || bi + n > b.length)
470
+ return false;
471
+ for (let j = 0; j < n; j++)
472
+ if (a[ai + j] !== b[bi + j])
473
+ return false;
474
+ return true;
475
+ }
476
+ // The sources of a parser's fixed tokens, while it lexes them.
477
+ function fixedTokens(config) {
478
+ return config.fixed.lex
479
+ ? Object.values(config.fixed.token)
480
+ .map((tin) => config.fixed.ref[tin])
481
+ .filter((src) => 'string' === typeof src && 0 < src.length)
482
+ : [];
483
+ }
484
+ const DECODER = new TextDecoder('utf-8', { ignoreBOM: true });
485
+ // The index of the first byte of the first invalid UTF-8 sequence, or -1.
486
+ function utf8Invalid(b) {
487
+ let i = 0;
488
+ const n = b.length;
489
+ while (i < n) {
490
+ const c = b[i];
491
+ if (c < 0x80) {
492
+ i++;
493
+ continue;
494
+ }
495
+ let need;
496
+ let min;
497
+ if (c >= 0xc2 && c <= 0xdf) {
498
+ need = 1;
499
+ min = 0x80;
500
+ }
501
+ else if (c >= 0xe0 && c <= 0xef) {
502
+ need = 2;
503
+ min = 0x800;
504
+ }
505
+ else if (c >= 0xf0 && c <= 0xf4) {
506
+ need = 3;
507
+ min = 0x10000;
508
+ }
509
+ else {
510
+ return i;
511
+ }
512
+ if (i + need >= n)
513
+ return i;
514
+ let cp = c & (0xff >> (need + 2));
515
+ for (let k = 1; k <= need; k++) {
516
+ const d = b[i + k];
517
+ if (undefined === d || (d & 0xc0) !== 0x80)
518
+ return i;
519
+ cp = (cp << 6) | (d & 0x3f);
520
+ }
521
+ if (cp < min || cp > 0x10ffff || (cp >= 0xd800 && cp <= 0xdfff))
522
+ return i;
523
+ i += need + 1;
524
+ }
525
+ return -1;
526
+ }
527
+ // An engine error in the record or chunk that starts on row `start`, at
528
+ // row `line` of the input. An abort names `start` and no column: it lands
529
+ // between two of the engine's steps, where the engine often holds no token
530
+ // to place it at and falls back to 1:1 of the text it was parsing, so its
531
+ // own position says nothing.
532
+ function lineFailure(error, line, start, abort) {
533
+ const fail = (0, source_1.engineFailure)(error, abort);
534
+ if ('ABORTED' === fail.code) {
535
+ fail.row = start;
536
+ }
537
+ else {
538
+ fail.row = line;
539
+ fail.col = (0, error_1.enginePosition)(error)?.col ?? 0;
540
+ }
541
+ return fail;
542
+ }
543
+ // A failure while the record or chunk that starts on row `start` was read:
544
+ // an abort that names no row names that one.
545
+ function atRecord(error, start) {
546
+ if (error instanceof error_1.Fail && 'ABORTED' === error.code && undefined === error.row) {
547
+ error.row = start;
548
+ }
549
+ return error;
550
+ }
551
+ // JSON Lines records from the input's pieces. A record ends at a line
552
+ // character outside a string, as the grammar's lexer ends one: a line
553
+ // character inside a string is the string's, which the grammar refuses
554
+ // there, so the record goes on into the next piece and fails whole as the
555
+ // grammar fails it. A record of nothing but the grammar's spaces is blank
556
+ // and skipped.
557
+ class JsonRecords {
558
+ constructor(parser, maxBytes) {
559
+ // The record being read: its text and bytes so far, the row it starts on
560
+ // (0 before its first piece), and whether a string is open in it.
561
+ this.text = '';
562
+ this.bytes = 0;
563
+ this.start = 0;
564
+ this.quote = null;
565
+ this.escaped = false;
566
+ const config = parser.internal().config;
567
+ this.line = lexed(config.line.lex, config.line.chars);
568
+ this.rows = Object.keys(config.line.rowChars ?? {});
569
+ this.single = !!config.line.single;
570
+ this.fixed = fixedTokens(config);
571
+ this.space = lexed(config.space.lex, config.space.chars);
572
+ this.quotes = lexed(config.string.lex, config.string.quoteMap);
573
+ this.escape = config.string.escChar ?? '\\';
574
+ this.maxBytes = maxBytes;
575
+ }
576
+ // A splitter for this grammar's line endings, bounded by the record.
577
+ splitter() {
578
+ const splitter = new PieceSplitter(this.line, this.rows, this.single, this.fixed);
579
+ splitter.budget = () => this.maxBytes - this.bytes;
580
+ splitter.over = (row) => {
581
+ const first = this.start || row;
582
+ return error_1.Fail.limit('max_record_bytes', this.maxBytes, `line ${first} is longer than ${this.maxBytes} bytes`).at(first, 1);
583
+ };
584
+ return splitter;
585
+ }
586
+ // Reads one piece, and hands `record` each record it ends that is not
587
+ // blank, with the row it starts on.
588
+ piece(row, text, bytes, record) {
589
+ if (0 === this.start)
590
+ this.start = row;
591
+ let end = -1;
592
+ for (let i = 0; i < text.length; i++) {
593
+ const c = text[i];
594
+ if (null !== this.quote) {
595
+ if (this.escaped)
596
+ this.escaped = false;
597
+ else if (c === this.escape)
598
+ this.escaped = true;
599
+ else if (c === this.quote)
600
+ this.quote = null;
601
+ }
602
+ else if (this.quotes.includes(c)) {
603
+ this.quote = c;
604
+ }
605
+ else if (this.line.includes(c)) {
606
+ end = i;
607
+ break;
608
+ }
609
+ }
610
+ if (end < 0) {
611
+ this.text += text;
612
+ this.bytes += bytes;
613
+ return 'continue';
614
+ }
615
+ return this.take(this.text + text.slice(0, end), record);
616
+ }
617
+ // The record the input ends inside, if any.
618
+ end(record) {
619
+ if (0 === this.start)
620
+ return 'continue';
621
+ return this.take(this.text, record);
622
+ }
623
+ take(text, record) {
624
+ const start = this.start;
625
+ this.text = '';
626
+ this.bytes = 0;
627
+ this.start = 0;
628
+ this.quote = null;
629
+ this.escaped = false;
630
+ for (const c of text) {
631
+ if (!this.space.includes(c))
632
+ return record(start, text);
633
+ }
634
+ return 'continue';
635
+ }
636
+ }
637
+ // JSON Lines through the walk.
638
+ class JsonlWalk {
639
+ constructor(sink, options) {
640
+ this.guarded = new guard_1.Guarded(sink, options.limits, options.abort, options.metrics);
641
+ this.abort = options.abort;
642
+ this.parser = (0, json_1.make)();
643
+ const abort = options.abort;
644
+ (0, rule_events_1.prepare)(this.parser, () => !abort.isAborted());
645
+ this.records = new JsonRecords(this.parser, options.limits.max_record_bytes);
646
+ this.splitter = this.records.splitter();
647
+ this.onRecord = (row, text) => this.record(row, text);
648
+ }
649
+ begin() {
650
+ return this.guarded.event(event_1.Ev.arrayStart);
651
+ }
652
+ piece(row, text, bytes) {
653
+ return this.records.piece(row, text, bytes, this.onRecord);
654
+ }
655
+ record(number, text) {
656
+ let value;
657
+ try {
658
+ value = this.parser.parse(text);
659
+ }
660
+ catch (err) {
661
+ throw lineFailure(err, number, number, this.abort);
662
+ }
663
+ try {
664
+ return (0, source_1.walkValue)(value, this.guarded);
665
+ }
666
+ catch (err) {
667
+ throw atRecord(err, number);
668
+ }
669
+ }
670
+ finish() {
671
+ if ('stop' === this.records.end(this.onRecord))
672
+ return 'stop';
673
+ if ('stop' === this.guarded.event(event_1.Ev.arrayEnd))
674
+ return 'stop';
675
+ return this.guarded.event(event_1.Ev.end);
676
+ }
677
+ close() {
678
+ this.guarded.flush();
679
+ }
680
+ }
681
+ // JSON Lines through the rule-event adapter: one parser, one subscriber,
682
+ // the adapter reset before each record.
683
+ class JsonlIncremental {
684
+ constructor(sink, options) {
685
+ // The record being read, which an abort the adapter raised names.
686
+ this.reading = 0;
687
+ const stop = new limits_1.AbortFlag();
688
+ const abort = options.abort;
689
+ this.abort = abort;
690
+ const adapter = new rule_events_1.Adapter(sink, options.limits, abort, options.metrics, source_1.Prune.never(), stop);
691
+ this.adapter = adapter;
692
+ this.parser = (0, json_1.make)();
693
+ (0, rule_events_1.prepare)(this.parser, () => !abort.isAborted() && !stop.isAborted(), (rule, done) => adapter.onDone(rule, done));
694
+ this.records = new JsonRecords(this.parser, options.limits.max_record_bytes);
695
+ this.splitter = this.records.splitter();
696
+ this.onRecord = (row, text) => this.record(row, text);
697
+ }
698
+ // The adapter's own outcome, when it stopped or failed inside a parse.
699
+ status() {
700
+ const status = this.adapter.status;
701
+ if ('failed' === status.type) {
702
+ throw 0 < this.reading ? atRecord(status.error, this.reading) : status.error;
703
+ }
704
+ if ('stopped' === status.type)
705
+ return 'stop';
706
+ return null;
707
+ }
708
+ begin() {
709
+ return this.adapter.send(event_1.Ev.arrayStart);
710
+ }
711
+ piece(row, text, bytes) {
712
+ return this.records.piece(row, text, bytes, this.onRecord);
713
+ }
714
+ record(number, text) {
715
+ this.reading = number;
716
+ let parsed;
717
+ try {
718
+ parsed = { ok: true, value: this.parser.parse(text) };
719
+ }
720
+ catch (error) {
721
+ parsed = { ok: false, error };
722
+ }
723
+ const status = this.status();
724
+ if (null !== status)
725
+ return status;
726
+ const adapter = this.adapter;
727
+ if (!parsed.ok)
728
+ throw lineFailure(parsed.error, number, number, this.abort);
729
+ if (adapter.complete()) {
730
+ adapter.reset();
731
+ return 'continue';
732
+ }
733
+ if (adapter.idle()) {
734
+ let flow;
735
+ try {
736
+ flow = adapter.walkWhole(parsed.value);
737
+ }
738
+ catch (err) {
739
+ throw atRecord(err, number);
740
+ }
741
+ if ('stop' === flow)
742
+ return 'stop';
743
+ adapter.reset();
744
+ return 'continue';
745
+ }
746
+ throw (0, rule_events_1.notStreamable)();
747
+ }
748
+ finish() {
749
+ if ('stop' === this.records.end(this.onRecord))
750
+ return 'stop';
751
+ const status = this.status();
752
+ if (null !== status)
753
+ return status;
754
+ if ('stop' === this.adapter.send(event_1.Ev.arrayEnd))
755
+ return 'stop';
756
+ return this.adapter.send(event_1.Ev.end);
757
+ }
758
+ close() {
759
+ this.adapter.sink.flush();
760
+ }
761
+ }
762
+ // What decides where the CSV grammar ends a record, read from its parser's
763
+ // resolved options and from the options the plugin builds its quote
764
+ // matcher from, so a separator, quote, record separator or comment setting
765
+ // moves the chunker's cut as it moves the grammar. Each set is empty while
766
+ // the engine does not lex its kind.
767
+ class Lexis {
768
+ constructor() {
769
+ // Line characters: outside a token, each ends a record.
770
+ this.line = [];
771
+ // Whether a run of line characters stops at a repeated one
772
+ // (`record.empty`), so that `\n\n` is two line tokens.
773
+ this.single = false;
774
+ this.rows = [];
775
+ this.space = [];
776
+ // The fixed tokens: the field separator, and outside strict mode the
777
+ // JSON structure characters.
778
+ this.fixed = [];
779
+ // The RFC 4180 quote, while the grammar's own matcher reads it: a
780
+ // doubled one inside is one quote, and a line character is text.
781
+ this.quote = null;
782
+ // The engine's own string quotes, after that one: an `escape` takes the
783
+ // next character whatever it is, and a line character is text only
784
+ // inside the `multi` ones (a backtick).
785
+ this.strings = [];
786
+ this.multi = [];
787
+ this.escape = '\\';
788
+ // The comment markers, longest first, each with its end: `null` for a
789
+ // line comment, which a line character ends without being part of.
790
+ this.comments = [];
791
+ // What ends a run of text, as characters and as token starts.
792
+ this.stops = [];
793
+ this.stopPrefixes = [];
794
+ // Whether the grammar ignores a space (outside strict mode) and a
795
+ // comment, so that a record of nothing else is blank.
796
+ this.spaceIgnored = false;
797
+ this.commentIgnored = false;
798
+ }
799
+ static csv(parser) {
800
+ const config = parser.internal().config;
801
+ const options = parser.options.plugin?.csv ?? {};
802
+ const lexis = new Lexis();
803
+ lexis.line = lexed(config.line.lex, config.line.chars);
804
+ lexis.single = !!config.line.single;
805
+ lexis.rows = Object.keys(config.line.rowChars ?? {});
806
+ lexis.space = lexed(config.space.lex, config.space.chars);
807
+ lexis.fixed = fixedTokens(config);
808
+ // The longest marker first, and a tie by name: the engine's order.
809
+ lexis.comments = config.comment.lex
810
+ ? Object.entries(config.comment.def)
811
+ .filter(([, def]) => def.lex && def.start)
812
+ .sort(([an, a], [bn, b]) => b.start.length - a.start.length || (an < bn ? -1 : an > bn ? 1 : 0))
813
+ .map(([, def]) => [def.start, def.line ? null : (def.end ?? '')])
814
+ : [];
815
+ // The RFC 4180 matcher as the plugin installs it: on in strict mode
816
+ // unless `string.csv` is false, off otherwise unless it is true, and
817
+ // inert for a quote that is not one UTF-16 code unit.
818
+ const matcher = options.strict ? false !== options.string?.csv : true === options.string?.csv;
819
+ const quote = options.string?.quote;
820
+ lexis.quote = matcher && 'string' === typeof quote && 1 === quote.length ? quote : null;
821
+ lexis.strings = lexed(config.string.lex, config.string.quoteMap);
822
+ lexis.multi = Object.keys(config.string.multiChars ?? {});
823
+ lexis.escape = config.string.escChar ?? '\\';
824
+ // What the engine's text matcher stops at: its ender pattern, which is
825
+ // the space and line characters, the fixed tokens, the comment markers
826
+ // and the grammar's own enders.
827
+ lexis.stops = [...lexis.space, ...lexis.line];
828
+ const enders = parser.options.ender;
829
+ lexis.stopPrefixes = [
830
+ ...lexis.fixed,
831
+ ...lexis.comments.map(([start]) => start),
832
+ ...('string' === typeof enders ? [...enders] : Array.isArray(enders) ? enders : []).filter((e) => 'string' === typeof e && 0 < e.length),
833
+ ];
834
+ const ignored = config.tokenSetTins?.IGNORE ?? {};
835
+ lexis.spaceIgnored = !!ignored[parser.token('#SP')];
836
+ lexis.commentIgnored = !!ignored[parser.token('#CM')];
837
+ return lexis;
838
+ }
839
+ // The length of the longest fixed token at `i`, or 0.
840
+ fixedAt(text, i) {
841
+ let len = 0;
842
+ for (const source of this.fixed) {
843
+ if (source.length > len && text.startsWith(source, i))
844
+ len = source.length;
845
+ }
846
+ return len;
847
+ }
848
+ // The comment that starts at `i`, by its index, or -1.
849
+ commentAt(text, i) {
850
+ return this.comments.findIndex(([start]) => text.startsWith(start, i));
851
+ }
852
+ // The length of the line token at `i`.
853
+ lineRun(text, i) {
854
+ let j = i;
855
+ while (j < text.length) {
856
+ const c = text[j];
857
+ if (!this.line.includes(c) || (this.single && text.slice(i, j).includes(c)))
858
+ break;
859
+ j++;
860
+ }
861
+ return j - i;
862
+ }
863
+ // Whether a run of text stops at `i`.
864
+ endsText(text, i) {
865
+ return this.stops.includes(text[i]) || this.stopPrefixes.some((p) => text.startsWith(p, i));
866
+ }
867
+ }
868
+ const START = { type: 'start' };
869
+ const TEXT = { type: 'text' };
870
+ const QUOTED = { type: 'quoted' };
871
+ // Follows CSV text through the grammar's tokens, a piece at a time, to
872
+ // find where its records end.
873
+ class Scanner {
874
+ constructor(lexis) {
875
+ this.at = START;
876
+ // Whether the record so far holds anything the grammar does not ignore,
877
+ // so that it is not blank.
878
+ this.content = false;
879
+ this.lexis = lexis;
880
+ }
881
+ // Scan one piece of the input, which ends just past a line ending unless
882
+ // it is the last, calling `end(offset, content)` where each record in it
883
+ // ends: `offset` is just past the line token that ends it, and `content`
884
+ // says whether it held anything the grammar does not ignore. Returns
885
+ // whether the piece ends a record, so that a chunk may close after it.
886
+ piece(text, end) {
887
+ const lexis = this.lexis;
888
+ let ended = false;
889
+ let i = 0;
890
+ while (i < text.length) {
891
+ const c = text[i];
892
+ ended = false;
893
+ const at = this.at;
894
+ switch (at.type) {
895
+ // In the lexer's order: the RFC 4180 matcher, fixed tokens, space,
896
+ // lines, strings, comments, and then a run of text.
897
+ case 'start': {
898
+ let len = 0;
899
+ if (lexis.quote === c) {
900
+ this.at = QUOTED;
901
+ this.content = true;
902
+ i++;
903
+ }
904
+ else if (0 < (len = lexis.fixedAt(text, i))) {
905
+ this.content = true;
906
+ i += len;
907
+ }
908
+ else if (lexis.space.includes(c)) {
909
+ this.content ||= !lexis.spaceIgnored;
910
+ i++;
911
+ }
912
+ else if (lexis.line.includes(c)) {
913
+ i += lexis.lineRun(text, i);
914
+ end(i, this.content);
915
+ this.content = false;
916
+ ended = true;
917
+ }
918
+ else if (lexis.strings.includes(c)) {
919
+ this.at = { type: 'str', quote: c, multi: lexis.multi.includes(c) };
920
+ this.content = true;
921
+ i++;
922
+ }
923
+ else {
924
+ const comment = lexis.commentAt(text, i);
925
+ if (0 <= comment) {
926
+ this.at = { type: 'comment', index: comment };
927
+ this.content ||= !lexis.commentIgnored;
928
+ i += lexis.comments[comment][0].length;
929
+ }
930
+ else {
931
+ // A character no token starts with (an ender) is one the
932
+ // grammar refuses, and lexing resumes after it.
933
+ if (!lexis.endsText(text, i))
934
+ this.at = TEXT;
935
+ this.content = true;
936
+ i++;
937
+ }
938
+ }
939
+ break;
940
+ }
941
+ case 'text':
942
+ if (lexis.endsText(text, i))
943
+ this.at = START;
944
+ else
945
+ i++;
946
+ break;
947
+ case 'quoted':
948
+ i++;
949
+ if (lexis.quote === c) {
950
+ if (text[i] === c)
951
+ i++;
952
+ else
953
+ this.at = START;
954
+ }
955
+ break;
956
+ case 'str':
957
+ if (c === at.quote) {
958
+ this.at = START;
959
+ i++;
960
+ }
961
+ else if (c === lexis.escape) {
962
+ i += i + 1 < text.length ? 2 : 1;
963
+ }
964
+ else if (!at.multi && lexis.line.includes(c)) {
965
+ // The grammar refuses the string here, so the line character
966
+ // is read as a line.
967
+ this.at = START;
968
+ }
969
+ else {
970
+ i++;
971
+ }
972
+ break;
973
+ case 'comment': {
974
+ const close = lexis.comments[at.index][1];
975
+ if (null !== close && '' !== close && text.startsWith(close, i)) {
976
+ this.at = START;
977
+ i += close.length;
978
+ }
979
+ else if (null === close && lexis.line.includes(c)) {
980
+ this.at = START;
981
+ }
982
+ else {
983
+ i++;
984
+ }
985
+ break;
986
+ }
987
+ }
988
+ }
989
+ return ended;
990
+ }
991
+ }
992
+ // The UTF-8 length of a string.
993
+ function utf8Bytes(text) {
994
+ return Buffer.byteLength(text, 'utf8');
995
+ }
996
+ // CSV through the walk: pieces into whole records, records into chunks,
997
+ // each chunk parsed with one reused parser.
998
+ class CsvWalk {
999
+ constructor(sink, options, header, grammar) {
1000
+ // The header record as the grammar reads it, once read, and its bytes:
1001
+ // every chunk after the first starts with it.
1002
+ this.header = null;
1003
+ this.headerBytes = 0;
1004
+ this.started = false;
1005
+ // The chunk being filled, while one is: its text and bytes; where it may
1006
+ // first close (past the header in front of it, or in the first chunk past
1007
+ // the header record itself), as an index and in bytes; the file row its
1008
+ // own text starts on, and the rows of the header in front of it; where
1009
+ // the text it cannot close inside starts (one record, or before the
1010
+ // header everything up to its end), in bytes and as a row; and where the
1011
+ // last record ended, which is where the next one starts.
1012
+ this.open = false;
1013
+ this.text = '';
1014
+ this.bytes = 0;
1015
+ this.body = 0;
1016
+ this.bodyBytes = 0;
1017
+ this.firstLine = 0;
1018
+ this.prefixLines = 0;
1019
+ this.recordAt = 0;
1020
+ this.recordRow = 0;
1021
+ this.lastEnd = 0;
1022
+ this.guarded = new guard_1.Guarded(sink, options.limits, options.abort, options.metrics);
1023
+ this.abort = options.abort;
1024
+ this.maxRecordBytes = options.limits.max_record_bytes;
1025
+ this.chunkBytes = options.chunkBytes;
1026
+ this.wantHeader = header;
1027
+ this.parser = (0, csv_1.make)({ ...grammar, header });
1028
+ this.recordEmpty = !!this.parser.options.plugin?.csv?.record?.empty;
1029
+ const abort = options.abort;
1030
+ (0, rule_events_1.prepare)(this.parser, () => !abort.isAborted());
1031
+ const lexis = Lexis.csv(this.parser);
1032
+ this.scanner = new Scanner(lexis);
1033
+ const splitter = new PieceSplitter(lexis.line, lexis.rows, lexis.single, lexis.fixed);
1034
+ splitter.budget = () => this.maxRecordBytes - (this.open ? this.bytes - this.recordAt : 0);
1035
+ splitter.over = (row) => {
1036
+ const first = (this.open && this.recordRow) || row;
1037
+ return error_1.Fail.limit('max_record_bytes', this.maxRecordBytes, `the record starting at line ${first} is longer than ${this.maxRecordBytes} bytes`).at(first, 1);
1038
+ };
1039
+ this.splitter = splitter;
1040
+ }
1041
+ begin() {
1042
+ return this.guarded.event(event_1.Ev.arrayStart);
1043
+ }
1044
+ // Opens a chunk: after the first, the header goes in front of it.
1045
+ openChunk() {
1046
+ this.open = true;
1047
+ this.text = this.started ? (this.header ?? '') : '';
1048
+ this.bytes = this.started ? this.headerBytes : 0;
1049
+ this.started = true;
1050
+ this.prefixLines = advance(this.scanner.lexis.rows, 0, 0, this.text)[0];
1051
+ this.body = this.text.length;
1052
+ this.bodyBytes = this.bytes;
1053
+ this.firstLine = 0;
1054
+ this.recordAt = this.bytes;
1055
+ this.recordRow = 0;
1056
+ this.lastEnd = 0;
1057
+ }
1058
+ piece(row, text, bytes) {
1059
+ if (!this.open)
1060
+ this.openChunk();
1061
+ if (0 === this.firstLine)
1062
+ this.firstLine = row;
1063
+ if (0 === this.recordRow)
1064
+ this.recordRow = row;
1065
+ const at = this.text.length;
1066
+ const atBytes = this.bytes;
1067
+ this.text += text;
1068
+ this.bytes += bytes;
1069
+ const seeking = this.wantHeader && null === this.header;
1070
+ const found = { from: -1, to: -1 };
1071
+ const ended = this.scanner.piece(text, (end, content) => {
1072
+ if (seeking && found.to < 0 && (content || this.recordEmpty)) {
1073
+ found.from = this.lastEnd;
1074
+ found.to = at + end;
1075
+ }
1076
+ this.lastEnd = at + end;
1077
+ });
1078
+ if (0 <= found.to) {
1079
+ const { from, to } = found;
1080
+ this.header = this.text.slice(from, to);
1081
+ this.headerBytes = utf8Bytes(this.header);
1082
+ this.body = to;
1083
+ this.bodyBytes = atBytes + utf8Bytes(text.slice(0, to - at));
1084
+ }
1085
+ // A chunk may close where a piece ends a record, once the header is
1086
+ // behind it.
1087
+ if (ended && !(this.wantHeader && null === this.header)) {
1088
+ this.recordAt = this.bytes;
1089
+ this.recordRow = 0;
1090
+ if (this.bytes > this.bodyBytes && this.bytes >= this.chunkBytes)
1091
+ return this.flush();
1092
+ }
1093
+ return 'continue';
1094
+ }
1095
+ // Parse the chunk and walk its records.
1096
+ flush() {
1097
+ if (!this.open)
1098
+ return 'continue';
1099
+ const text = this.text;
1100
+ const firstLine = this.firstLine;
1101
+ const prefixLines = this.prefixLines;
1102
+ this.open = false;
1103
+ this.text = '';
1104
+ let value;
1105
+ try {
1106
+ value = this.parser.parse(text);
1107
+ }
1108
+ catch (err) {
1109
+ const pos = (0, error_1.enginePosition)(err);
1110
+ const rowInChunk = Math.max(pos?.row ?? 0, 1);
1111
+ // An error in the prepended header itself is the file's first line.
1112
+ const line = rowInChunk > prefixLines ? firstLine + rowInChunk - prefixLines - 1 : 1;
1113
+ throw lineFailure(err, line, firstLine, this.abort);
1114
+ }
1115
+ // The grammar returns an array of records; anything else has none.
1116
+ const records = Array.isArray(value) ? value : [];
1117
+ try {
1118
+ for (const record of records) {
1119
+ if ('stop' === (0, source_1.walkValue)(record, this.guarded))
1120
+ return 'stop';
1121
+ }
1122
+ }
1123
+ catch (err) {
1124
+ throw atRecord(err, firstLine);
1125
+ }
1126
+ return 'continue';
1127
+ }
1128
+ finish() {
1129
+ if ('stop' === this.flush())
1130
+ return 'stop';
1131
+ if ('stop' === this.guarded.event(event_1.Ev.arrayEnd))
1132
+ return 'stop';
1133
+ return this.guarded.event(event_1.Ev.end);
1134
+ }
1135
+ close() {
1136
+ this.guarded.flush();
1137
+ }
1138
+ }
1139
+ //# sourceMappingURL=lines.js.map