Skip to main content

tabnas_transduce/source/
lines.rs

1//! [`LinesSource`]: JSON Lines and CSV a record (or a chunk of records) at
2//! a time, from any [`BufRead`].
3//!
4//! The engine parses a whole `&str`, so a document is retained at least
5//! once however it is consumed. Line-delimited formats do not need to be
6//! one document: JSON Lines is one value per line, and CSV is a header
7//! plus independent records, so each can be parsed a piece at a time with
8//! one reused parser and the memory a run needs stops depending on the
9//! file's size. That is what this source does, and the only reason it
10//! exists: its events are exactly the whole-file parse's (the array of
11//! the per-line values, or of the records), which the chunk-boundary
12//! tests hold it to at every byte.
13//!
14//! Both formats cut the input where the grammar ends a record, reading
15//! the text as the grammar's own lexer does (with the line characters,
16//! quotes, separators and comments its parser's resolved options set), and
17//! nothing read is left unparsed but a blank JSON Lines record, which the
18//! grammar skips too.
19//!
20//! JSON Lines: each record is parsed with one `tabnas-json` parser. A
21//! record is the text between line characters outside a string, so a lone
22//! `\r` ends one as `\n` does. A record of nothing but the grammar's
23//! spaces (a space or a tab) is blank and skipped; any other is parsed, so
24//! a line holding a form feed or a no-break space fails as the grammar
25//! fails it. With an owned sink ([`LinesSource::run_owned`]) the record
26//! goes through the rule-event adapter, reset per record, so numbers keep
27//! their lexemes; with a borrowed sink ([`Source::run`]) the value is
28//! walked and numbers carry none. A record that does not parse is
29//! `INPUT_INVALID` with its line's number as the row.
30//!
31//! CSV: the input is cut into chunks of whole records. A quote opens a
32//! quoted field only where the lexer starts a token (a record's start,
33//! after the field separator or a space), so a quote inside a field is
34//! that field's text; the configured quote reads `""` as one quote; a
35//! quoted field, a backtick string and a block comment may span lines; and
36//! a line character anywhere else, a lone `\r` as much as `\n`, ends a
37//! record. The
38//! header record, the first record the grammar reads (a blank or comment
39//! line before it is none), is kept and prepended to every chunk after the
40//! first when `header` is on, so the reused `tabnas-csv` parser names each
41//! record's fields as the whole file would. A chunk closes at the first
42//! record boundary past [`DEFAULT_CHUNK_BYTES`] (or the configured size),
43//! so one chunk, and never a fraction of a record, is what a parse holds.
44//! An input that ends inside a quoted field fails with the grammar's
45//! `unterminated_string`, in the header as anywhere.
46//!
47//! The reader is taken a piece at a time, and a piece ends just past each
48//! of the grammar's line endings: one of its line characters (a lone `\r`
49//! or a configured separator as much as `\n`), a `\r\n`, or under
50//! `record.empty` the whole line token the lexer reads. A line character
51//! inside one of the grammar's fixed tokens (a field separator such as
52//! `"\n~"`) is the token's and ends no piece. So a record never waits for
53//! a `\n` to end, and a chunk can close after any record, however many of
54//! them one `\n`-terminated line holds.
55//!
56//! Memory is bounded by one chunk, and a record is never split, so a
57//! single record larger than `max_record_bytes` (a record with its line
58//! ending; for the CSV header, everything up to its end) fails with that
59//! limit's name rather than growing a chunk without bound.
60//! The bound holds while the record is READ, not only once it is whole: a
61//! record is taken from the reader a buffer at a time and refused the
62//! moment it passes the limit, so an unterminated record of any length
63//! costs one buffer beyond the limit and no more. `max_depth`,
64//! `max_key_bytes` and `max_scalar_bytes` apply to the events as
65//! everywhere. `max_record_bytes` counts the record's source bytes here,
66//! where a table transducer downstream would count its retained bytes; it
67//! is the same idea of "one row" measured before it is parsed.
68
69use std::io::{self, BufRead};
70use std::sync::{Arc, Mutex};
71
72use tabnas::Tabnas;
73use tabnas_csv::CsvOptions;
74
75use crate::error::{Code, Fail};
76use crate::event::JsonEvent;
77use crate::limits::{AbortFlag, Limits, Metrics};
78use crate::sink::{Flow, Sink};
79use crate::source::guard::Guarded;
80use crate::source::rule_events::{self, Adapter, Status, GUARD};
81use crate::source::{engine_failure, walk_value, Prune, Source};
82
83/// How much of the input one CSV parse holds, at most one record over.
84pub const DEFAULT_CHUNK_BYTES: usize = 256 * 1024;
85
86/// The line-delimited format to read.
87#[derive(Clone, Debug)]
88pub enum LineFormat {
89    /// One JSON value per line; blank lines are skipped.
90    Jsonl,
91    /// CSV records. `header` says whether the first record names the
92    /// fields (it overrides `options.header`); the other options are the
93    /// grammar's (`object: false` for arrays instead of objects, and so
94    /// on), boxed because they are large and the format is passed around.
95    Csv {
96        header: bool,
97        options: Box<CsvOptions>,
98    },
99}
100
101impl LineFormat {
102    /// CSV with a header line and the grammar's default options.
103    pub fn csv() -> LineFormat {
104        LineFormat::Csv {
105            header: true,
106            options: Box::new(CsvOptions::default()),
107        }
108    }
109
110    /// CSV with the given options; `header` decides whether the first
111    /// record names the fields.
112    pub fn csv_with(header: bool, options: CsvOptions) -> LineFormat {
113        LineFormat::Csv {
114            header,
115            options: Box::new(options),
116        }
117    }
118}
119
120/// A line-delimited reader as a source.
121pub struct LinesSource<R: BufRead> {
122    reader: R,
123    format: LineFormat,
124    limits: Limits,
125    abort: AbortFlag,
126    metrics: Arc<Metrics>,
127    chunk_bytes: usize,
128}
129
130impl<R: BufRead> LinesSource<R> {
131    pub fn new(reader: R, format: LineFormat) -> LinesSource<R> {
132        LinesSource {
133            reader,
134            format,
135            limits: Limits::default(),
136            abort: AbortFlag::new(),
137            metrics: Metrics::new(),
138            chunk_bytes: DEFAULT_CHUNK_BYTES,
139        }
140    }
141
142    pub fn limits(mut self, limits: Limits) -> Self {
143        self.limits = limits;
144        self
145    }
146
147    pub fn abort(mut self, abort: AbortFlag) -> Self {
148        self.abort = abort;
149        self
150    }
151
152    pub fn metrics(mut self, metrics: Arc<Metrics>) -> Self {
153        self.metrics = metrics;
154        self
155    }
156
157    /// The CSV chunk size; a chunk closes at the first record boundary at
158    /// or past it. Zero closes a chunk at every record.
159    pub fn chunk_bytes(mut self, bytes: usize) -> Self {
160        self.chunk_bytes = bytes;
161        self
162    }
163
164    /// Run with an owned sink: JSON Lines through the rule-event adapter
165    /// (lexemes kept), CSV through the walk. The sink comes back.
166    pub fn run_owned<S: Sink + Send + 'static>(self, sink: S) -> (Result<Flow, Fail>, S) {
167        match &self.format {
168            LineFormat::Jsonl => self.jsonl_incremental(sink),
169            LineFormat::Csv { .. } => {
170                let mut guarded =
171                    Guarded::new(sink, &self.limits, self.abort.clone(), self.metrics.clone());
172                let outcome = self.drive(&mut guarded);
173                (outcome, guarded.into_inner())
174            }
175        }
176    }
177
178    /// [`LinesSource::run_owned`] for a boxed sink.
179    pub fn run_boxed(
180        self,
181        sink: Box<dyn Sink + Send>,
182    ) -> (Result<Flow, Fail>, Box<dyn Sink + Send>) {
183        self.run_owned(sink)
184    }
185
186    /// The borrowed-sink drive: both formats through the walk.
187    fn drive<S: Sink>(self, guarded: &mut Guarded<S>) -> Result<Flow, Fail> {
188        let LinesSource {
189            reader,
190            format,
191            limits,
192            abort,
193            chunk_bytes,
194            ..
195        } = self;
196        match format {
197            LineFormat::Jsonl => {
198                let mut parser = tabnas_json::make();
199                install_guard(&mut parser, &abort);
200                let mut records = JsonRecords::new(reader, &parser, limits.max_record_bytes);
201                if guarded.event(JsonEvent::ArrayStart)? == Flow::Stop {
202                    return Ok(Flow::Stop);
203                }
204                while let Some((number, record)) = records.next_record()? {
205                    let value = parser
206                        .parse(record)
207                        .map_err(|e| line_failure(&e, number, number, &abort))?;
208                    if walk_value(&value, guarded).map_err(|fail| at_record(fail, number))?
209                        == Flow::Stop
210                    {
211                        return Ok(Flow::Stop);
212                    }
213                }
214                if guarded.event(JsonEvent::ArrayEnd)? == Flow::Stop {
215                    return Ok(Flow::Stop);
216                }
217                guarded.event(JsonEvent::End)
218            }
219            LineFormat::Csv { header, options } => {
220                let mut options = *options;
221                options.header = header;
222                let mut parser = tabnas_csv::make_with(options.clone());
223                install_guard(&mut parser, &abort);
224                let mut chunks = Chunks::new(
225                    reader,
226                    &parser,
227                    &options,
228                    chunk_bytes,
229                    limits.max_record_bytes,
230                );
231                if guarded.event(JsonEvent::ArrayStart)? == Flow::Stop {
232                    return Ok(Flow::Stop);
233                }
234                while let Some(chunk) = chunks.next_chunk()? {
235                    let value = parser
236                        .parse(&chunk.text)
237                        .map_err(|e| chunk.failure(&e, &abort))?;
238                    for record in records_of(&value) {
239                        if walk_value(record, guarded)
240                            .map_err(|fail| at_record(fail, chunk.first_line))?
241                            == Flow::Stop
242                        {
243                            return Ok(Flow::Stop);
244                        }
245                    }
246                }
247                if guarded.event(JsonEvent::ArrayEnd)? == Flow::Stop {
248                    return Ok(Flow::Stop);
249                }
250                guarded.event(JsonEvent::End)
251            }
252        }
253    }
254
255    /// JSON Lines with the adapter: one parser, one subscriber, the
256    /// adapter reset before each line.
257    fn jsonl_incremental<S: Sink + Send + 'static>(self, sink: S) -> (Result<Flow, Fail>, S) {
258        let LinesSource {
259            reader,
260            limits,
261            abort,
262            metrics,
263            ..
264        } = self;
265        let stop = AbortFlag::new();
266        let adapter = Adapter::new(
267            sink,
268            &limits,
269            abort.clone(),
270            metrics,
271            &Prune::Never,
272            stop.clone(),
273        );
274        let shared = Arc::new(Mutex::new(adapter));
275        let mut parser = tabnas_json::make();
276        Adapter::install(
277            &mut parser,
278            Arc::downgrade(&shared),
279            abort.clone(),
280            stop.clone(),
281        );
282        let mut records = JsonRecords::new(reader, &parser, limits.max_record_bytes);
283        // The record being read, which an abort the adapter raised names.
284        let mut reading = None;
285        let mut outcome = rule_events::lock(&shared).send(JsonEvent::ArrayStart);
286        if outcome.as_ref().is_ok_and(|flow| *flow == Flow::Continue) {
287            outcome = loop {
288                let (number, record) = match records.next_record() {
289                    Ok(Some(next)) => next,
290                    Ok(None) => break Ok(Flow::Continue),
291                    Err(fail) => break Err(fail),
292                };
293                reading = Some(number);
294                let parsed = parser.parse(record);
295                let mut adapter = rule_events::lock(&shared);
296                match adapter.status() {
297                    Status::Running => {}
298                    Status::Stopped => break Ok(Flow::Stop),
299                    // Reported from the adapter's own status below.
300                    Status::Failed(_) => break Ok(Flow::Continue),
301                }
302                match parsed {
303                    Ok(_) if adapter.complete() => adapter.reset(),
304                    Ok(value) if adapter.idle() => match adapter.walk_whole(&value) {
305                        Ok(Flow::Continue) => adapter.reset(),
306                        Ok(Flow::Stop) => break Ok(Flow::Stop),
307                        Err(fail) => break Err(at_record(fail, number)),
308                    },
309                    Ok(_) => break Err(rule_events::not_streamable()),
310                    Err(e) => break Err(line_failure(&e, number, number, &abort)),
311                }
312            };
313        }
314        drop(parser);
315        let mut adapter = rule_events::take(shared);
316        if let (Ok(Flow::Continue), Status::Running) = (&outcome, adapter.status()) {
317            outcome = adapter
318                .send(JsonEvent::ArrayEnd)
319                .and_then(|flow| match flow {
320                    Flow::Continue => adapter.send(JsonEvent::End),
321                    Flow::Stop => Ok(Flow::Stop),
322                });
323        }
324        let (status, sink) = adapter.finish();
325        let outcome = match status {
326            Status::Failed(fail) => Err(match reading {
327                Some(number) => at_record(fail, number),
328                None => fail,
329            }),
330            Status::Stopped => Ok(Flow::Stop),
331            Status::Running => outcome,
332        };
333        (outcome, sink)
334    }
335}
336
337impl<R: BufRead> Source for LinesSource<R> {
338    /// Both formats through the walk; JSON Lines numbers carry no lexeme
339    /// on this path (see [`LinesSource::run_owned`]).
340    fn run(self, sink: &mut dyn Sink) -> Result<Flow, Fail> {
341        let mut guarded =
342            Guarded::new(sink, &self.limits, self.abort.clone(), self.metrics.clone());
343        let outcome = self.drive(&mut guarded);
344        guarded.flush();
345        outcome
346    }
347}
348
349fn install_guard(parser: &mut Tabnas, abort: &AbortFlag) {
350    let flag = abort.clone();
351    parser.parse_guard(GUARD, move |_ctx| !flag.is_aborted());
352}
353
354/// An engine error in the record or chunk that starts on row `start`, at
355/// row `line` of the input. An abort names `start` and no column: it lands
356/// between two of the engine's steps, where the engine often holds no
357/// token to place it at and falls back to 1:1 of the text it was parsing,
358/// so its own position says nothing.
359fn line_failure(error: &tabnas::TabnasError, line: u64, start: u64, abort: &AbortFlag) -> Fail {
360    let mut fail = engine_failure(error, abort);
361    if fail.code == Code::Aborted {
362        fail.row = Some(start);
363    } else {
364        fail.row = Some(line);
365        fail.column = Some(error.col as u64);
366    }
367    fail
368}
369
370/// A failure while the record or chunk that starts on row `start` was
371/// read: an abort that names no row names that one.
372fn at_record(mut fail: Fail, start: u64) -> Fail {
373    if fail.code == Code::Aborted && fail.row.is_none() {
374        fail.row = Some(start);
375    }
376    fail
377}
378
379/// The records of a parsed CSV chunk: the elements of its array.
380fn records_of(value: &tabnas::Value) -> &[tabnas::Value] {
381    match value {
382        tabnas::Value::Array(items) => items,
383        tabnas::Value::ListRef(list) => &list.value,
384        // The grammar returns an array; anything else has no records.
385        _ => &[],
386    }
387}
388
389/// What the JSON Lines grammar's lexer makes of a line, read from the line
390/// parser's resolved options (the grammar is the JSON one with the line
391/// token made significant): a line character outside a string ends a
392/// record, and a record of nothing but space is blank. Each set is empty
393/// while the engine does not lex its kind.
394struct JsonLexis {
395    line: Vec<char>,
396    space: Vec<char>,
397    quotes: Vec<char>,
398    escape: char,
399}
400
401impl JsonLexis {
402    fn of(parser: &Tabnas) -> JsonLexis {
403        let config = parser.config();
404        let mut line = lexed(config.line.lex, &config.line.chars);
405        if config.line.lex {
406            line.extend(&config.line.fixed);
407        }
408        JsonLexis {
409            line,
410            space: lexed(config.space.lex, &config.space.chars),
411            quotes: lexed(config.string.lex, &config.string.chars),
412            escape: config.string.escape_char,
413        }
414    }
415}
416
417/// JSON Lines records from the input's pieces. A record ends at a line
418/// character outside a string, as the grammar's lexer ends one: a line
419/// character inside a string is the string's, which the grammar refuses
420/// there, so the record goes on into the next piece and fails whole as the
421/// grammar fails it. A record of nothing but the grammar's spaces is blank
422/// and skipped.
423struct JsonRecords<R: BufRead> {
424    pieces: Pieces<R>,
425    lexis: JsonLexis,
426    record: String,
427    max_bytes: usize,
428}
429
430impl<R: BufRead> JsonRecords<R> {
431    fn new(reader: R, parser: &Tabnas, max_bytes: usize) -> JsonRecords<R> {
432        let lexis = JsonLexis::of(parser);
433        let config = parser.config();
434        let fixed: Vec<String> = config
435            .fixed
436            .tokens
437            .values()
438            .filter(|token| config.fixed.lex && !token.source.is_empty())
439            .map(|token| token.source.clone())
440            .collect();
441        let pieces = Pieces::new(
442            reader,
443            lexis.line.clone(),
444            config.line.row_chars.chars().collect(),
445            config.line.single,
446            &fixed,
447        );
448        JsonRecords {
449            pieces,
450            lexis,
451            record: String::new(),
452            max_bytes,
453        }
454    }
455
456    /// The next record that is not blank, without its line ending, and
457    /// the row it starts on; `None` at the end of the input. A record
458    /// with its line ending longer than `max_bytes` fails with that limit.
459    fn next_record(&mut self) -> Result<Option<(u64, &str)>, Fail> {
460        let (start, end) = loop {
461            self.record.clear();
462            let mut start = None;
463            let mut quote = None;
464            let mut escaped = false;
465            let mut end = None;
466            while end.is_none() {
467                let first = start.unwrap_or(self.pieces.row);
468                let max = self.max_bytes;
469                let over = || {
470                    Fail::limit(
471                        "max_record_bytes",
472                        max as u64,
473                        format!("line {first} is longer than {max} bytes"),
474                    )
475                    .at(first, 1)
476                };
477                let budget = max.saturating_sub(self.record.len());
478                let Some((row, piece)) = self.pieces.next_piece(budget, over)? else {
479                    break;
480                };
481                start.get_or_insert(row);
482                let held = self.record.len();
483                for (at, c) in piece.char_indices() {
484                    if let Some(open) = quote {
485                        if escaped {
486                            escaped = false;
487                        } else if c == self.lexis.escape {
488                            escaped = true;
489                        } else if c == open {
490                            quote = None;
491                        }
492                    } else if self.lexis.quotes.contains(&c) {
493                        quote = Some(c);
494                    } else if self.lexis.line.contains(&c) {
495                        end = Some(held + at);
496                        break;
497                    }
498                }
499                self.record.push_str(piece);
500            }
501            let Some(start) = start else {
502                return Ok(None);
503            };
504            let end = end.unwrap_or(self.record.len());
505            if !self.record[..end]
506                .chars()
507                .all(|c| self.lexis.space.contains(&c))
508            {
509                break (start, end);
510            }
511        };
512        Ok(Some((start, &self.record[..end])))
513    }
514}
515
516/// The characters of `chars` while the engine lexes their kind, else none.
517fn lexed(on: bool, chars: &str) -> Vec<char> {
518    if on {
519        chars.chars().collect()
520    } else {
521        Vec::new()
522    }
523}
524
525/// The input from a reader in pieces, each ending just past a line
526/// ending: one of the grammar's line characters, a `\r\n`, or under
527/// `line.single` (`record.empty`) the whole line token the lexer reads,
528/// every line character up to a repeated one. A line character inside one
529/// of the grammar's fixed tokens (a field separator such as `"\n~"`) is the
530/// token's and ends no piece, and where a fixed token holds one, a piece
531/// takes the whole run of line characters the lexer reads as one token,
532/// so that no piece starts inside a line token. A record therefore never
533/// ends inside a piece, only at its end. Each piece carries the row the
534/// engine gives its first character: one more than the row characters
535/// before it.
536struct Pieces<R: BufRead> {
537    reader: R,
538    /// Bytes read and not yet handed out: the piece being read starts at
539    /// `start`, and holds no line ending before `scanned`.
540    pending: Vec<u8>,
541    start: usize,
542    scanned: usize,
543    eof: bool,
544    /// The line characters, their UTF-8 forms, and the bytes that end a
545    /// form.
546    line: Vec<char>,
547    forms: Vec<Vec<u8>>,
548    ends: [bool; 256],
549    single: bool,
550    /// The fixed tokens that hold a line character, as UTF-8.
551    spanning: Vec<Vec<u8>>,
552    /// The line token being followed when what was read ran out, to pick
553    /// up where it stopped: where it ends so far, and its characters.
554    following: Option<(usize, Vec<char>)>,
555    rows: Vec<char>,
556    /// Where the next piece starts: its row, and its byte offset in it.
557    row: u64,
558    offset: u64,
559}
560
561impl<R: BufRead> Pieces<R> {
562    fn new(
563        reader: R,
564        line: Vec<char>,
565        rows: Vec<char>,
566        single: bool,
567        fixed: &[String],
568    ) -> Pieces<R> {
569        let forms: Vec<Vec<u8>> = line.iter().map(|c| c.to_string().into_bytes()).collect();
570        let mut ends = [false; 256];
571        for form in &forms {
572            ends[usize::from(form[form.len() - 1])] = true;
573        }
574        let spanning = fixed
575            .iter()
576            .filter(|token| token.chars().any(|c| line.contains(&c)))
577            .map(|token| token.as_bytes().to_vec())
578            .collect();
579        Pieces {
580            reader,
581            pending: Vec::new(),
582            start: 0,
583            scanned: 0,
584            eof: false,
585            line,
586            forms,
587            ends,
588            single,
589            spanning,
590            following: None,
591            rows,
592            row: 1,
593            offset: 0,
594        }
595    }
596
597    /// The next piece and the row it starts on, or `None` at the end of
598    /// the input. It is taken from the reader a buffer at a time and
599    /// refused with `over`'s failure the moment it passes `budget` bytes,
600    /// so a piece without an end costs one buffer past the budget and no
601    /// more.
602    fn next_piece(
603        &mut self,
604        budget: usize,
605        over: impl Fn() -> Fail,
606    ) -> Result<Option<(u64, &str)>, Fail> {
607        let end = loop {
608            if let Some(end) = self.ending() {
609                break end;
610            }
611            if self.pending.len() - self.start > budget {
612                return Err(over());
613            }
614            if !self.more()? {
615                // At the end of the input every question has its answer.
616                if let Some(end) = self.ending() {
617                    break end;
618                }
619                if self.pending.len() == self.start {
620                    return Ok(None);
621                }
622                break self.pending.len();
623            }
624        };
625        if end - self.start > budget {
626            return Err(over());
627        }
628        let (from, row) = (self.start, self.row);
629        self.start = end;
630        self.scanned = end;
631        let piece = match std::str::from_utf8(&self.pending[from..end]) {
632            Ok(piece) => piece,
633            Err(e) => {
634                let valid = std::str::from_utf8(&self.pending[from..from + e.valid_up_to()])
635                    .unwrap_or_default();
636                let (row, offset) = advance(&self.rows, self.row, self.offset, valid);
637                return Err(Fail::input(format!(
638                    "line {row} is not UTF-8 from its byte {}",
639                    offset + 1
640                ))
641                .at(row, offset + 1));
642            }
643        };
644        (self.row, self.offset) = advance(&self.rows, self.row, self.offset, piece);
645        Ok(Some((row, piece)))
646    }
647
648    /// Reads one more buffer of the input onto what is pending, letting go
649    /// of what was handed out; false at the end of the input.
650    fn more(&mut self) -> Result<bool, Fail> {
651        if self.eof {
652            return Ok(false);
653        }
654        if self.start > 0 {
655            self.pending.drain(..self.start);
656            self.scanned -= self.start;
657            if let Some((end, _)) = &mut self.following {
658                *end -= self.start;
659            }
660            self.start = 0;
661        }
662        loop {
663            match self.reader.fill_buf() {
664                Ok([]) => {
665                    self.eof = true;
666                    return Ok(false);
667                }
668                Ok(available) => {
669                    let n = available.len();
670                    self.pending.extend_from_slice(available);
671                    self.reader.consume(n);
672                    return Ok(true);
673                }
674                Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
675                Err(e) => return Err(Fail::input(format!("reading line {}: {e}", self.row))),
676            }
677        }
678    }
679
680    /// Where the piece being read ends: just past the first line ending
681    /// after `scanned`, or `None` while what is read holds none, or cannot
682    /// yet tell where one ends.
683    fn ending(&mut self) -> Option<usize> {
684        if let Some((end, token)) = self.following.take() {
685            return self.follow(end, token);
686        }
687        let mut from = self.scanned.max(self.start);
688        while let Some(at) = self.pending[from..]
689            .iter()
690            .position(|&b| self.ends[usize::from(b)])
691        {
692            let end = from + at + 1;
693            let Some(k) = self
694                .forms
695                .iter()
696                .position(|form| self.pending[self.start..end].ends_with(form))
697            else {
698                from = end;
699                continue;
700            };
701            let at = end - self.forms[k].len();
702            match self.in_fixed(at, k) {
703                Some(true) => {
704                    from = end;
705                    continue;
706                }
707                Some(false) => {}
708                None => {
709                    self.scanned = at;
710                    return None;
711                }
712            }
713            return self.follow(end, vec![self.line[k]]);
714        }
715        self.scanned = self.pending.len();
716        None
717    }
718
719    /// Whether the `k`th line character, at `at`, lies inside one of the
720    /// fixed tokens that hold a line character; `None` while what is read
721    /// cannot yet tell.
722    fn in_fixed(&self, at: usize, k: usize) -> Option<bool> {
723        let form = &self.forms[k];
724        let mut unsure = false;
725        for token in &self.spanning {
726            for j in 0..token.len() {
727                if !token[j..].starts_with(form) {
728                    continue;
729                }
730                let Some(token_start) = at.checked_sub(j).filter(|&s| s >= self.start) else {
731                    continue;
732                };
733                if self.pending[token_start..at] != token[..j] {
734                    continue;
735                }
736                let rest = &token[j + form.len()..];
737                let after = at + form.len();
738                let have = &self.pending[after..(after + rest.len()).min(self.pending.len())];
739                if !rest.starts_with(have) {
740                    continue;
741                }
742                if have.len() == rest.len() {
743                    return Some(true);
744                }
745                unsure |= !self.eof;
746            }
747        }
748        if unsure {
749            None
750        } else {
751            Some(false)
752        }
753    }
754
755    /// Where the line token whose characters so far are `token`, ending at
756    /// `end`, ends: under `line.single` it takes every line character not
757    /// yet in it, as the lexer reads one; where a fixed token holds a line
758    /// character, every line character, as the lexer reads a run; otherwise
759    /// a `\n` after a `\r`, so that `\r\n` is one line ending and a record
760    /// is held to its own line. `None` while what is read cannot yet tell,
761    /// with the token kept to pick up from once more is read, so a long run
762    /// is followed once and not again from its start on every read.
763    fn follow(&mut self, mut end: usize, mut token: Vec<char>) -> Option<usize> {
764        'token: loop {
765            let mut unsure = false;
766            for (i, form) in self.forms.iter().enumerate() {
767                let c = self.line[i];
768                let goes_on = if self.single {
769                    !token.contains(&c)
770                } else {
771                    !self.spanning.is_empty() || (token == ['\r'] && c == '\n')
772                };
773                if !goes_on {
774                    continue;
775                }
776                let have = &self.pending[end..(end + form.len()).min(self.pending.len())];
777                if !form.starts_with(have) {
778                    continue;
779                }
780                if have.len() == form.len() {
781                    end += form.len();
782                    token.push(c);
783                    continue 'token;
784                }
785                unsure |= !self.eof;
786            }
787            if unsure {
788                self.following = Some((end, token));
789                return None;
790            }
791            return Some(end);
792        }
793    }
794}
795
796/// The row, and the byte offset in it, just after `text`, which starts at
797/// `row` and `offset`: a row character starts the next row.
798fn advance(rows: &[char], row: u64, offset: u64, text: &str) -> (u64, u64) {
799    let mut count = 0;
800    let mut last = None;
801    for (at, c) in text.char_indices() {
802        if rows.contains(&c) {
803            count += 1;
804            last = Some(at + c.len_utf8());
805        }
806    }
807    match last {
808        Some(end) => (row + count, (text.len() - end) as u64),
809        None => (row, offset + text.len() as u64),
810    }
811}
812
813/// One CSV chunk ready to parse: whole records, the header prepended when
814/// it is not the chunk that carried it.
815struct Chunk {
816    text: String,
817    /// The file line the chunk's own text, after `prefix_lines`, starts on.
818    first_line: u64,
819    /// Lines in `text` before the chunk's own: those of the prepended
820    /// header in a chunk after the first, and none in the first, which is
821    /// the file's text from its start.
822    prefix_lines: u64,
823}
824
825impl Chunk {
826    /// An engine error inside the chunk, at the file's line.
827    fn failure(&self, error: &tabnas::TabnasError, abort: &AbortFlag) -> Fail {
828        let row_in_chunk = (error.row as u64).max(1);
829        let line = if row_in_chunk > self.prefix_lines {
830            self.first_line + row_in_chunk - self.prefix_lines - 1
831        } else {
832            // The error is in the prepended header itself.
833            1
834        };
835        line_failure(error, line, self.first_line, abort)
836    }
837}
838
839/// Cuts a CSV reader into chunks of whole records, where the grammar ends
840/// them.
841struct Chunks<R: BufRead> {
842    pieces: Pieces<R>,
843    scanner: Scanner,
844    /// Whether the first record names the fields, and whether a blank line
845    /// is a record (`record.empty`): together, which record the header is.
846    want_header: bool,
847    record_empty: bool,
848    /// The header record as the grammar reads it, once read: every chunk
849    /// after the first starts with it.
850    header: Option<String>,
851    chunk_bytes: usize,
852    max_record_bytes: usize,
853    started: bool,
854    done: bool,
855}
856
857impl<R: BufRead> Chunks<R> {
858    fn new(
859        reader: R,
860        parser: &Tabnas,
861        options: &CsvOptions,
862        chunk_bytes: usize,
863        max_record_bytes: usize,
864    ) -> Chunks<R> {
865        let lexis = Lexis::csv(parser, options);
866        let config = parser.config();
867        let pieces = Pieces::new(
868            reader,
869            lexis.line.clone(),
870            config.line.row_chars.chars().collect(),
871            config.line.single,
872            &lexis.fixed,
873        );
874        Chunks {
875            pieces,
876            scanner: Scanner::new(lexis),
877            want_header: options.header,
878            record_empty: options.record.empty,
879            header: None,
880            chunk_bytes,
881            max_record_bytes,
882            started: false,
883            done: false,
884        }
885    }
886
887    /// The next chunk, or `None` once nothing is left to read. Every piece
888    /// read goes into a chunk, the first one's header included, so every
889    /// byte of the input is parsed.
890    fn next_chunk(&mut self) -> Result<Option<Chunk>, Fail> {
891        if self.done {
892            return Ok(None);
893        }
894        let mut text = String::new();
895        if self.started {
896            text.push_str(self.header.as_deref().unwrap_or_default());
897        }
898        self.started = true;
899        let prefix_lines = advance(&self.pieces.rows, 0, 0, &text).0;
900        // The chunk closes only past `body`: past the header in front of
901        // it, or in the first chunk past the header record itself.
902        let mut body = text.len();
903        let mut first_line = None;
904        // The text a chunk cannot close inside, from its offset and row:
905        // one record, or before the header everything up to its end.
906        let mut open = (text.len(), None);
907        // Where the last record ended, which is where the next one starts.
908        let mut last_end = 0;
909        loop {
910            let first = open.1.unwrap_or(self.pieces.row);
911            let max = self.max_record_bytes;
912            let over = || {
913                Fail::limit(
914                    "max_record_bytes",
915                    max as u64,
916                    format!("the record starting at line {first} is longer than {max} bytes"),
917                )
918                .at(first, 1)
919            };
920            let budget = max.saturating_sub(text.len() - open.0);
921            let Some((number, piece)) = self.pieces.next_piece(budget, over)? else {
922                self.done = true;
923                break;
924            };
925            first_line.get_or_insert(number);
926            open.1.get_or_insert(number);
927            let at = text.len();
928            text.push_str(piece);
929            let seeking = self.want_header && self.header.is_none();
930            let record_empty = self.record_empty;
931            let mut found = None;
932            let ended = self.scanner.piece(piece, |end, content| {
933                if seeking && found.is_none() && (content || record_empty) {
934                    found = Some((last_end, at + end));
935                }
936                last_end = at + end;
937            });
938            if let Some((from, to)) = found {
939                self.header = Some(text[from..to].to_string());
940                body = to;
941            }
942            // A chunk may close where a piece ends a record, once the
943            // header is behind it.
944            if ended && !(self.want_header && self.header.is_none()) {
945                open = (text.len(), None);
946                if text.len() > body && text.len() >= self.chunk_bytes {
947                    break;
948                }
949            }
950        }
951        let Some(first_line) = first_line else {
952            // Nothing was left to read.
953            return Ok(None);
954        };
955        Ok(Some(Chunk {
956            text,
957            first_line,
958            prefix_lines,
959        }))
960    }
961}
962
963/// What decides where the CSV grammar ends a record, read from its
964/// parser's resolved options and from the options the plugin builds its
965/// quote matcher from, so a separator, quote, record separator or comment
966/// setting moves the chunker's cut as it moves the grammar. Each set is
967/// empty while the engine does not lex its kind.
968struct Lexis {
969    /// Line characters: outside a token, each ends a record.
970    line: Vec<char>,
971    /// Whether a run of line characters stops at a repeated one
972    /// (`record.empty`), so that `\n\n` is two line tokens.
973    single: bool,
974    space: Vec<char>,
975    /// The fixed tokens: the field separator, and outside strict mode the
976    /// JSON structure characters.
977    fixed: Vec<String>,
978    /// The RFC 4180 quote, while the grammar's own matcher reads it: a
979    /// doubled one inside is one quote, and a line character is text.
980    quote: Option<char>,
981    /// The engine's own string quotes, after that one: an `escape` takes
982    /// the next character whatever it is, and a line character is text
983    /// only inside the `multi` ones (a backtick).
984    strings: Vec<char>,
985    multi: Vec<char>,
986    escape: char,
987    /// The comment markers, longest first, each with its end: `None` for a
988    /// line comment, which a line character ends without being part of.
989    comments: Vec<(String, Option<String>)>,
990    /// What ends a run of text, as characters and as token starts.
991    stops: Vec<char>,
992    stop_prefixes: Vec<String>,
993    /// Whether the grammar ignores a space (outside strict mode) and a
994    /// comment, so that a record of nothing else is blank.
995    space_ignored: bool,
996    comment_ignored: bool,
997}
998
999impl Lexis {
1000    fn csv(parser: &Tabnas, options: &CsvOptions) -> Lexis {
1001        let config = parser.config();
1002        let mut line = lexed(config.line.lex, &config.line.chars);
1003        if config.line.lex {
1004            line.extend(&config.line.fixed);
1005        }
1006        let space = lexed(config.space.lex, &config.space.chars);
1007        let fixed: Vec<String> = config
1008            .fixed
1009            .tokens
1010            .values()
1011            .filter(|token| config.fixed.lex && !token.source.is_empty())
1012            .map(|token| token.source.clone())
1013            .collect();
1014        // The longest marker first, and a tie by name: the engine's order.
1015        let mut comments: Vec<_> = config
1016            .comment
1017            .definitions
1018            .iter()
1019            .filter(|(_, comment)| config.comment.lex && comment.lex && !comment.start.is_empty())
1020            .collect();
1021        comments.sort_by(|(name, comment), (other_name, other)| {
1022            other
1023                .start
1024                .len()
1025                .cmp(&comment.start.len())
1026                .then_with(|| name.cmp(other_name))
1027        });
1028        let comments: Vec<(String, Option<String>)> = comments
1029            .into_iter()
1030            .map(|(_, comment)| {
1031                (
1032                    comment.start.clone(),
1033                    (!comment.line).then(|| comment.end.clone()),
1034                )
1035            })
1036            .collect();
1037        // The RFC 4180 matcher as the plugin installs it: on in strict mode
1038        // unless `string.csv` is false, off otherwise unless it is true, and
1039        // inert for a quote that is not one UTF-16 code unit.
1040        let matcher = if options.strict {
1041            options.string.csv != Some(false)
1042        } else {
1043            options.string.csv == Some(true)
1044        };
1045        let mut quote = options.string.quote.chars();
1046        let quote = match (quote.next(), quote.next()) {
1047            (Some(quote), None) if matcher && quote.len_utf16() == 1 => Some(quote),
1048            _ => None,
1049        };
1050        let ignored = parser.token_set("IGNORE").unwrap_or_default();
1051        // What the engine's text matcher stops at; it refuses the two
1052        // Unicode line separators while it lexes lines.
1053        let mut stops = space.clone();
1054        stops.extend(&line);
1055        if config.line.lex {
1056            stops.extend(['\u{2028}', '\u{2029}']);
1057        }
1058        let mut stop_prefixes = fixed.clone();
1059        stop_prefixes.extend(comments.iter().map(|(start, _)| start.clone()));
1060        stop_prefixes.extend(config.ender.iter().filter(|e| !e.is_empty()).cloned());
1061        Lexis {
1062            line,
1063            single: config.line.single,
1064            space,
1065            fixed,
1066            quote,
1067            strings: lexed(config.string.lex, &config.string.chars),
1068            multi: config.string.multi_chars.chars().collect(),
1069            escape: config.string.escape_char,
1070            comments,
1071            stops,
1072            stop_prefixes,
1073            space_ignored: ignored.contains(&tabnas::TIN_SP),
1074            comment_ignored: ignored.contains(&tabnas::TIN_CM),
1075        }
1076    }
1077
1078    /// The length of the longest fixed token at the head of `rest`.
1079    fn fixed_at(&self, rest: &str) -> Option<usize> {
1080        self.fixed
1081            .iter()
1082            .filter(|source| rest.starts_with(source.as_str()))
1083            .map(String::len)
1084            .max()
1085    }
1086
1087    /// The comment that starts at the head of `rest`, by its index.
1088    fn comment_at(&self, rest: &str) -> Option<usize> {
1089        self.comments
1090            .iter()
1091            .position(|(start, _)| rest.starts_with(start.as_str()))
1092    }
1093
1094    /// The length of the line token at the head of `rest`.
1095    fn line_run(&self, rest: &str) -> usize {
1096        let mut len = 0;
1097        for (at, c) in rest.char_indices() {
1098            if !self.line.contains(&c) || (self.single && rest[..at].contains(c)) {
1099                break;
1100            }
1101            len = at + c.len_utf8();
1102        }
1103        len
1104    }
1105
1106    /// Whether a run of text stops at the head of `rest`, whose first
1107    /// character is `c`.
1108    fn ends_text(&self, rest: &str, c: char) -> bool {
1109        self.stops.contains(&c)
1110            || self
1111                .stop_prefixes
1112                .iter()
1113                .any(|prefix| rest.starts_with(prefix.as_str()))
1114    }
1115}
1116
1117/// Where the record scanner is, between two characters.
1118#[derive(Clone, Copy)]
1119enum At {
1120    /// Where the lexer starts a token.
1121    Start,
1122    /// Inside text, a number or a keyword: a run that only what `ends_text`
1123    /// names ends, so a quote inside one is text.
1124    Text,
1125    /// Inside an RFC 4180 quoted field.
1126    Quoted,
1127    /// Inside one of the engine's own strings.
1128    Str { quote: char, multi: bool },
1129    /// Inside a comment, by its index in `Lexis::comments`.
1130    Comment(usize),
1131}
1132
1133/// Follows CSV text through the grammar's tokens, a piece at a time, to
1134/// find where its records end.
1135struct Scanner {
1136    lexis: Lexis,
1137    at: At,
1138    /// Whether the record so far holds anything the grammar does not
1139    /// ignore, so that it is not blank.
1140    content: bool,
1141}
1142
1143impl Scanner {
1144    fn new(lexis: Lexis) -> Scanner {
1145        Scanner {
1146            lexis,
1147            at: At::Start,
1148            content: false,
1149        }
1150    }
1151
1152    /// Scan one piece of the input, which ends just past a line ending
1153    /// unless it is the last, calling `end(offset, content)` where each
1154    /// record in it ends: `offset` is just past the line token that ends
1155    /// it, and `content` says whether it held anything the grammar does not
1156    /// ignore. Returns whether the piece ends a record, so that a chunk may
1157    /// close after it.
1158    fn piece(&mut self, line: &str, mut end: impl FnMut(usize, bool)) -> bool {
1159        let lexis = &self.lexis;
1160        let mut ended = false;
1161        let mut i = 0;
1162        while let Some(c) = line[i..].chars().next() {
1163            let rest = &line[i..];
1164            ended = false;
1165            match self.at {
1166                // In the lexer's order: the RFC 4180 matcher, fixed tokens,
1167                // space, lines, strings, comments, and then a run of text.
1168                At::Start => {
1169                    if lexis.quote == Some(c) {
1170                        self.at = At::Quoted;
1171                        self.content = true;
1172                        i += c.len_utf8();
1173                    } else if let Some(len) = lexis.fixed_at(rest) {
1174                        self.content = true;
1175                        i += len;
1176                    } else if lexis.space.contains(&c) {
1177                        self.content |= !lexis.space_ignored;
1178                        i += c.len_utf8();
1179                    } else if lexis.line.contains(&c) {
1180                        i += lexis.line_run(rest);
1181                        end(i, self.content);
1182                        self.content = false;
1183                        ended = true;
1184                    } else if lexis.strings.contains(&c) {
1185                        self.at = At::Str {
1186                            quote: c,
1187                            multi: lexis.multi.contains(&c),
1188                        };
1189                        self.content = true;
1190                        i += c.len_utf8();
1191                    } else if let Some(comment) = lexis.comment_at(rest) {
1192                        self.at = At::Comment(comment);
1193                        self.content |= !lexis.comment_ignored;
1194                        i += lexis.comments[comment].0.len();
1195                    } else {
1196                        // A character no token starts with (an ender) is
1197                        // one the grammar refuses, and lexing resumes after it.
1198                        if !lexis.ends_text(rest, c) {
1199                            self.at = At::Text;
1200                        }
1201                        self.content = true;
1202                        i += c.len_utf8();
1203                    }
1204                }
1205                At::Text => {
1206                    if lexis.ends_text(rest, c) {
1207                        self.at = At::Start;
1208                    } else {
1209                        i += c.len_utf8();
1210                    }
1211                }
1212                At::Quoted => {
1213                    i += c.len_utf8();
1214                    if lexis.quote == Some(c) {
1215                        if line[i..].starts_with(c) {
1216                            i += c.len_utf8();
1217                        } else {
1218                            self.at = At::Start;
1219                        }
1220                    }
1221                }
1222                At::Str { quote, multi } => {
1223                    if c == quote {
1224                        self.at = At::Start;
1225                        i += c.len_utf8();
1226                    } else if c == lexis.escape {
1227                        i += c.len_utf8();
1228                        i += line[i..].chars().next().map_or(0, char::len_utf8);
1229                    } else if !multi && lexis.line.contains(&c) {
1230                        // The grammar refuses the string here, so the line
1231                        // character is read as a line.
1232                        self.at = At::Start;
1233                    } else {
1234                        i += c.len_utf8();
1235                    }
1236                }
1237                At::Comment(comment) => match &lexis.comments[comment].1 {
1238                    Some(close) if !close.is_empty() && rest.starts_with(close.as_str()) => {
1239                        self.at = At::Start;
1240                        i += close.len();
1241                    }
1242                    None if lexis.line.contains(&c) => self.at = At::Start,
1243                    _ => i += c.len_utf8(),
1244                },
1245            }
1246        }
1247        ended
1248    }
1249}
1250
1251#[cfg(test)]
1252mod tests {
1253    use super::*;
1254    use crate::event::OwnedJsonEvent;
1255    use crate::sink::FnSink;
1256    use crate::source::ValueSource;
1257    use std::io::{self, Cursor, Read};
1258
1259    /// A reader that hands out at most `step` bytes per fill, so every
1260    /// buffer boundary the line reader could meet is met.
1261    struct Trickle {
1262        data: Vec<u8>,
1263        pos: usize,
1264        step: usize,
1265    }
1266
1267    impl Read for Trickle {
1268        fn read(&mut self, out: &mut [u8]) -> io::Result<usize> {
1269            let n = self.step.min(out.len()).min(self.data.len() - self.pos);
1270            out[..n].copy_from_slice(&self.data[self.pos..self.pos + n]);
1271            self.pos += n;
1272            Ok(n)
1273        }
1274    }
1275
1276    fn trickle(data: &str, step: usize) -> io::BufReader<Trickle> {
1277        io::BufReader::with_capacity(
1278            step.max(1),
1279            Trickle {
1280                data: data.as_bytes().to_vec(),
1281                pos: 0,
1282                step: step.max(1),
1283            },
1284        )
1285    }
1286
1287    fn without_lexemes(events: &[OwnedJsonEvent]) -> Vec<OwnedJsonEvent> {
1288        events
1289            .iter()
1290            .map(|e| match e {
1291                OwnedJsonEvent::Number { value, .. } => OwnedJsonEvent::Number {
1292                    value: *value,
1293                    lexeme: None,
1294                },
1295                other => other.clone(),
1296            })
1297            .collect()
1298    }
1299
1300    fn walked(value: &tabnas::Value) -> Vec<OwnedJsonEvent> {
1301        let mut rec = Vec::new();
1302        ValueSource(value).run(&mut rec).unwrap();
1303        rec
1304    }
1305
1306    const JSONL: &str = "{\"a\":1.50,\"b\":[true,null]}\r\n\n  \n{\"a\":2,\"b\":\"x\"}\n[3]\n\"s\"";
1307
1308    /// The JSON Lines source installs the adapter on `tabnas-json` itself,
1309    /// without the `ParserSource` gate, which is sound only while json is
1310    /// verified.
1311    #[test]
1312    fn the_line_sources_grammar_is_verified() {
1313        assert!(crate::source::capability::incremental("json"));
1314    }
1315
1316    #[test]
1317    fn jsonl_matches_the_whole_file_parse_at_every_reader_boundary() {
1318        let want = walked(&tabnas_jsonl::parse(JSONL).unwrap());
1319        for step in 1..=JSONL.len() {
1320            let mut rec: Vec<OwnedJsonEvent> = Vec::new();
1321            LinesSource::new(trickle(JSONL, step), LineFormat::Jsonl)
1322                .run(&mut rec)
1323                .unwrap();
1324            assert_eq!(rec, want, "borrowed, step {step}");
1325
1326            let (r, rec) = LinesSource::new(trickle(JSONL, step), LineFormat::Jsonl)
1327                .run_owned(Vec::<OwnedJsonEvent>::new());
1328            assert_eq!(r.unwrap(), Flow::Continue);
1329            assert_eq!(without_lexemes(&rec), want, "owned, step {step}");
1330            assert!(
1331                rec.contains(&OwnedJsonEvent::Number {
1332                    value: 1.5,
1333                    lexeme: Some("1.50".into())
1334                }),
1335                "the owned path keeps lexemes"
1336            );
1337        }
1338    }
1339
1340    #[test]
1341    fn a_bad_jsonl_line_names_its_line_number() {
1342        let text = "{\"a\":1}\n\n{\"a\": }\n{\"a\":2}\n";
1343        let mut rec: Vec<OwnedJsonEvent> = Vec::new();
1344        let err = LinesSource::new(Cursor::new(text), LineFormat::Jsonl)
1345            .run(&mut rec)
1346            .unwrap_err();
1347        assert_eq!(err.code, Code::InputInvalid);
1348        assert_eq!(err.row, Some(3));
1349        assert_eq!(err.column, Some(7));
1350        let (r, _) = LinesSource::new(Cursor::new(text), LineFormat::Jsonl)
1351            .run_owned(Vec::<OwnedJsonEvent>::new());
1352        let err = r.unwrap_err();
1353        assert_eq!(err.code, Code::InputInvalid);
1354        assert_eq!(err.row, Some(3));
1355    }
1356
1357    #[test]
1358    fn empty_input_is_an_empty_array_for_both_formats() {
1359        for format in [LineFormat::Jsonl, LineFormat::csv()] {
1360            let mut rec: Vec<OwnedJsonEvent> = Vec::new();
1361            LinesSource::new(Cursor::new(""), format.clone())
1362                .run(&mut rec)
1363                .unwrap();
1364            assert_eq!(
1365                rec,
1366                vec![
1367                    OwnedJsonEvent::ArrayStart,
1368                    OwnedJsonEvent::ArrayEnd,
1369                    OwnedJsonEvent::End
1370                ]
1371            );
1372            let (r, rec) = LinesSource::new(Cursor::new("\n\n"), format)
1373                .run_owned(Vec::<OwnedJsonEvent>::new());
1374            r.unwrap();
1375            assert_eq!(rec.len(), 3);
1376        }
1377    }
1378
1379    const CSV: &str =
1380        "id,note,n\r\n1,\"multi\r\nline, with \"\"quotes\"\"\",2.50\r\n3,plain,4\r\n\r\n5,\"a\",6";
1381
1382    #[test]
1383    fn csv_matches_the_whole_file_parse_at_every_chunk_size_and_reader_boundary() {
1384        let want = walked(&tabnas_csv::parse(CSV).unwrap());
1385        assert_eq!(
1386            want.iter()
1387                .filter(|e| matches!(e, OwnedJsonEvent::ObjectStart))
1388                .count(),
1389            3
1390        );
1391        for chunk_bytes in 0..=CSV.len() + 1 {
1392            let mut rec: Vec<OwnedJsonEvent> = Vec::new();
1393            LinesSource::new(Cursor::new(CSV), LineFormat::csv())
1394                .chunk_bytes(chunk_bytes)
1395                .run(&mut rec)
1396                .unwrap();
1397            assert_eq!(rec, want, "chunk {chunk_bytes}");
1398        }
1399        for step in 1..=CSV.len() {
1400            let (r, rec) = LinesSource::new(trickle(CSV, step), LineFormat::csv())
1401                .chunk_bytes(7)
1402                .run_owned(Vec::<OwnedJsonEvent>::new());
1403            r.unwrap();
1404            assert_eq!(rec, want, "step {step}");
1405        }
1406    }
1407
1408    #[test]
1409    fn csv_without_a_header_yields_the_grammars_records() {
1410        let text = "1,2\n3,\"4\n5\"\n";
1411        for object in [true, false] {
1412            let options = CsvOptions {
1413                header: false,
1414                object,
1415                ..CsvOptions::default()
1416            };
1417            let want = walked(&tabnas_csv::make_with(options.clone()).parse(text).unwrap());
1418            for chunk_bytes in 0..=text.len() {
1419                let mut rec: Vec<OwnedJsonEvent> = Vec::new();
1420                LinesSource::new(
1421                    Cursor::new(text),
1422                    LineFormat::csv_with(false, options.clone()),
1423                )
1424                .chunk_bytes(chunk_bytes)
1425                .run(&mut rec)
1426                .unwrap();
1427                assert_eq!(rec, want, "object {object}, chunk {chunk_bytes}");
1428            }
1429        }
1430    }
1431
1432    #[test]
1433    fn a_header_only_file_is_an_empty_table() {
1434        let mut rec: Vec<OwnedJsonEvent> = Vec::new();
1435        LinesSource::new(Cursor::new("a,b\n"), LineFormat::csv())
1436            .run(&mut rec)
1437            .unwrap();
1438        assert_eq!(rec.len(), 3);
1439    }
1440
1441    #[test]
1442    fn a_bad_csv_record_names_its_file_line() {
1443        let text = "a,b\n1,2\n3,\"x\n4,5\n";
1444        let mut rec: Vec<OwnedJsonEvent> = Vec::new();
1445        let err = LinesSource::new(Cursor::new(text), LineFormat::csv())
1446            .chunk_bytes(0)
1447            .run(&mut rec)
1448            .unwrap_err();
1449        assert_eq!(err.code, Code::InputInvalid);
1450        assert_eq!(err.row, Some(3), "{err}");
1451        let err = LinesSource::new(Cursor::new(text), LineFormat::csv())
1452            .run(&mut Vec::<OwnedJsonEvent>::new())
1453            .unwrap_err();
1454        assert_eq!(err.row, Some(3), "{err}");
1455    }
1456
1457    /// Counts the bytes handed out, so a test can see how far past the
1458    /// limit a reader was pulled.
1459    struct Counting<R> {
1460        inner: R,
1461        read: usize,
1462    }
1463
1464    impl<R: Read> Read for Counting<R> {
1465        fn read(&mut self, out: &mut [u8]) -> io::Result<usize> {
1466            let n = self.inner.read(out)?;
1467            self.read += n;
1468            Ok(n)
1469        }
1470    }
1471
1472    /// A reader that repeats one byte without end and never a newline.
1473    struct Endless(u8);
1474
1475    impl Read for Endless {
1476        fn read(&mut self, out: &mut [u8]) -> io::Result<usize> {
1477            out.fill(self.0);
1478            Ok(out.len())
1479        }
1480    }
1481
1482    #[test]
1483    fn an_unterminated_line_is_refused_at_the_limit_not_after_being_read_whole() {
1484        const BUFFER: usize = 4096;
1485        let limits = Limits {
1486            max_record_bytes: 8,
1487            ..Limits::default()
1488        };
1489        for format in [LineFormat::Jsonl, LineFormat::csv()] {
1490            let mut reader = io::BufReader::with_capacity(
1491                BUFFER,
1492                Counting {
1493                    inner: Endless(b'a'),
1494                    read: 0,
1495                },
1496            );
1497            let err = LinesSource::new(&mut reader, format.clone())
1498                .limits(limits.clone())
1499                .run(&mut Vec::<OwnedJsonEvent>::new())
1500                .unwrap_err();
1501            assert_eq!(err.limit.as_ref().unwrap().name, "max_record_bytes");
1502            assert_eq!(err.row, Some(1));
1503            let read = reader.into_inner().read;
1504            assert!(
1505                read <= limits.max_record_bytes + BUFFER,
1506                "{format:?}: {read} bytes were pulled from an endless line"
1507            );
1508        }
1509    }
1510
1511    #[test]
1512    fn an_oversized_record_names_max_record_bytes() {
1513        let limits = Limits {
1514            max_record_bytes: 8,
1515            ..Limits::default()
1516        };
1517        let err = LinesSource::new(
1518            Cursor::new("{\"a\":1}\n{\"a\":123456}\n"),
1519            LineFormat::Jsonl,
1520        )
1521        .limits(limits.clone())
1522        .run(&mut Vec::<OwnedJsonEvent>::new())
1523        .unwrap_err();
1524        assert_eq!(err.limit.as_ref().unwrap().name, "max_record_bytes");
1525        assert_eq!(err.row, Some(2));
1526        let err = LinesSource::new(
1527            Cursor::new("a\n\"long\nquoted\nfield\"\n"),
1528            LineFormat::csv(),
1529        )
1530        .limits(limits)
1531        .run(&mut Vec::<OwnedJsonEvent>::new())
1532        .unwrap_err();
1533        assert_eq!(err.limit.as_ref().unwrap().name, "max_record_bytes");
1534        assert_eq!(err.row, Some(2));
1535    }
1536
1537    #[test]
1538    fn a_stop_and_an_abort_end_the_run_on_both_paths() {
1539        let text = "{\"a\":1}\n{\"a\":2}\n{\"a\":3}\n";
1540        let stopper = || {
1541            let mut n = 0;
1542            FnSink(move |_ev: JsonEvent<'_>| {
1543                n += 1;
1544                Ok(if n == 4 { Flow::Stop } else { Flow::Continue })
1545            })
1546        };
1547        let mut sink = stopper();
1548        assert_eq!(
1549            LinesSource::new(Cursor::new(text), LineFormat::Jsonl)
1550                .run(&mut sink)
1551                .unwrap(),
1552            Flow::Stop
1553        );
1554        let (r, _) = LinesSource::new(Cursor::new(text), LineFormat::Jsonl).run_owned(stopper());
1555        assert_eq!(r.unwrap(), Flow::Stop);
1556
1557        let abort = AbortFlag::new();
1558        abort.abort();
1559        let err = LinesSource::new(Cursor::new(text), LineFormat::Jsonl)
1560            .abort(abort.clone())
1561            .run(&mut Vec::<OwnedJsonEvent>::new())
1562            .unwrap_err();
1563        assert_eq!(err.code, Code::Aborted);
1564        let (r, _) = LinesSource::new(Cursor::new(text), LineFormat::Jsonl)
1565            .abort(abort)
1566            .run_owned(Vec::<OwnedJsonEvent>::new());
1567        assert_eq!(r.unwrap_err().code, Code::Aborted);
1568    }
1569
1570    #[test]
1571    fn an_abort_names_the_row_the_record_or_chunk_it_was_reading_starts_on() {
1572        // An abort lands between two of the engine's steps, where it has no
1573        // position of its own, so the run names the row the record (JSON
1574        // Lines) or chunk (CSV) it was reading starts on, and no column.
1575        let aborter = |abort: AbortFlag, after: usize| {
1576            let mut n = 0;
1577            FnSink(move |_ev: JsonEvent<'_>| {
1578                n += 1;
1579                if n == after {
1580                    abort.abort();
1581                }
1582                Ok(Flow::Continue)
1583            })
1584        };
1585        let raised = |already: bool| {
1586            let abort = AbortFlag::new();
1587            if already {
1588                abort.abort();
1589            }
1590            abort
1591        };
1592        // Raised with the second record's first event, which the owned
1593        // path emits during that record's parse and the borrowed one while
1594        // it walks the value: the run stops in that record, which starts on
1595        // row 3. Already raised: the run's first event fails, before any
1596        // record is read, and names no row.
1597        let text = "{\"a\":1}\n\n{\"a\":2}\n{\"a\":3}\n";
1598        for (after, row) in [(6, Some(3)), (0, None)] {
1599            let abort = raised(after == 0);
1600            let err = LinesSource::new(Cursor::new(text), LineFormat::Jsonl)
1601                .abort(abort.clone())
1602                .run(&mut aborter(abort, after))
1603                .unwrap_err();
1604            assert_eq!(
1605                (err.code, err.row, err.column),
1606                (Code::Aborted, row, None),
1607                "{err}"
1608            );
1609            let abort = raised(after == 0);
1610            let (r, _) = LinesSource::new(Cursor::new(text), LineFormat::Jsonl)
1611                .abort(abort.clone())
1612                .run_owned(aborter(abort, after));
1613            let err = r.unwrap_err();
1614            assert_eq!(
1615                (err.code, err.row, err.column),
1616                (Code::Aborted, row, None),
1617                "{err}"
1618            );
1619        }
1620        // CSV: the first row of the chunk, with a chunk for each record, and
1621        // with one chunk for the whole text.
1622        for (chunk_bytes, row) in [(0, 3), (DEFAULT_CHUNK_BYTES, 1)] {
1623            let abort = AbortFlag::new();
1624            let err = LinesSource::new(Cursor::new("a\n1\n2\n3\n"), LineFormat::csv())
1625                .abort(abort.clone())
1626                .chunk_bytes(chunk_bytes)
1627                .run(&mut aborter(abort, 5))
1628                .unwrap_err();
1629            assert_eq!(
1630                (err.code, err.row, err.column),
1631                (Code::Aborted, Some(row), None),
1632                "chunk {chunk_bytes}: {err}"
1633            );
1634        }
1635    }
1636
1637    #[test]
1638    fn invalid_utf8_is_invalid_input_at_its_line() {
1639        let bytes: &[u8] = b"{\"a\":1}\n{\"a\":\"\xff\"}\n";
1640        let err = LinesSource::new(Cursor::new(bytes), LineFormat::Jsonl)
1641            .run(&mut Vec::<OwnedJsonEvent>::new())
1642            .unwrap_err();
1643        assert_eq!(err.code, Code::InputInvalid);
1644        assert_eq!(err.row, Some(2));
1645    }
1646
1647    /// A reading of one text: its events, or its failure as the engine's
1648    /// code, row and column.
1649    type Reading = Result<Vec<OwnedJsonEvent>, (String, Option<u64>, Option<u64>)>;
1650
1651    /// The whole parse's reading, which the line source must reproduce.
1652    fn whole(parsed: Result<tabnas::Value, tabnas::TabnasError>) -> Reading {
1653        match parsed {
1654            Ok(value) => Ok(walked(&value)),
1655            Err(e) => Err((e.code.to_string(), Some(e.row as u64), Some(e.col as u64))),
1656        }
1657    }
1658
1659    /// The line source's reading. Its failure carries the engine's code at
1660    /// the head of its message.
1661    fn reading(outcome: Result<Flow, Fail>, events: Vec<OwnedJsonEvent>) -> Reading {
1662        match outcome {
1663            Ok(flow) => {
1664                assert_eq!(flow, Flow::Continue);
1665                Ok(events)
1666            }
1667            Err(fail) => {
1668                assert_eq!(fail.code, Code::InputInvalid, "{fail}");
1669                let code = fail.message.split(':').next().unwrap_or_default();
1670                Err((code.to_string(), fail.row, fail.column))
1671            }
1672        }
1673    }
1674
1675    /// Holds the CSV line source to the whole parse of `text` through the
1676    /// same grammar options, at chunk sizes from a chunk per record (0) to
1677    /// the whole text and, on the owned path, at reader boundaries too, and
1678    /// returns that reading.
1679    fn csv_streams_as_whole(text: &str, options: &CsvOptions) -> Reading {
1680        let want = whole(tabnas_csv::make_with(options.clone()).parse(text));
1681        let format = LineFormat::csv_with(options.header, options.clone());
1682        let sizes =
1683            (0..=text.len() + 1).filter(|&n| n < 4 || n.is_power_of_two() || n >= text.len());
1684        for chunk_bytes in sizes {
1685            let mut rec: Vec<OwnedJsonEvent> = Vec::new();
1686            let outcome = LinesSource::new(Cursor::new(text), format.clone())
1687                .chunk_bytes(chunk_bytes)
1688                .run(&mut rec);
1689            assert_eq!(reading(outcome, rec), want, "{text:?}, chunk {chunk_bytes}");
1690        }
1691        for step in [1, 3] {
1692            let (outcome, rec) = LinesSource::new(trickle(text, step), format.clone())
1693                .chunk_bytes(0)
1694                .run_owned(Vec::<OwnedJsonEvent>::new());
1695            assert_eq!(reading(outcome, rec), want, "{text:?}, step {step}");
1696        }
1697        want
1698    }
1699
1700    /// Holds the JSON Lines source to the whole parse of `text` by the
1701    /// JSON Lines grammar, on both paths, and returns that reading.
1702    fn jsonl_streams_as_whole(text: &str) -> Reading {
1703        let want = whole(tabnas_jsonl::parse(text));
1704        let mut rec: Vec<OwnedJsonEvent> = Vec::new();
1705        let outcome = LinesSource::new(Cursor::new(text), LineFormat::Jsonl).run(&mut rec);
1706        assert_eq!(reading(outcome, rec), want, "borrowed, {text:?}");
1707        let (outcome, rec) = LinesSource::new(Cursor::new(text), LineFormat::Jsonl)
1708            .run_owned(Vec::<OwnedJsonEvent>::new());
1709        let rec = without_lexemes(&rec);
1710        assert_eq!(reading(outcome, rec), want, "owned, {text:?}");
1711        want
1712    }
1713
1714    fn objects(events: &[OwnedJsonEvent]) -> usize {
1715        events
1716            .iter()
1717            .filter(|e| matches!(e, OwnedJsonEvent::ObjectStart))
1718            .count()
1719    }
1720
1721    /// tabnas-csv's vendored corpus file
1722    /// `papa-misplaced-quotes-in-data-twice-not-as-opening-quotes.csv`, byte
1723    /// for byte. A quote inside a field is that field's text, not the start
1724    /// of a quoted field, so the grammar reads two lines, the header and a
1725    /// record. The chunker took each quote for an opening or a closing one,
1726    /// read both lines as the header, and never parsed them: an empty table
1727    /// and success (tabnas/transduce#13).
1728    const MISPLACED_QUOTES: &str = "A,B\",C\nD,E\",F";
1729
1730    #[test]
1731    fn quotes_inside_a_field_are_read_as_the_grammar_reads_them() {
1732        let header = CsvOptions::default();
1733        let want = csv_streams_as_whole(MISPLACED_QUOTES, &header).unwrap();
1734        assert_eq!(objects(&want), 1, "the header and one record: {want:?}");
1735        // The corpus's own options: no header, each record an array.
1736        let corpus = CsvOptions {
1737            header: false,
1738            object: false,
1739            ..CsvOptions::default()
1740        };
1741        csv_streams_as_whole(MISPLACED_QUOTES, &corpus).unwrap();
1742        // Records after it, one with a quote inside a field and then a
1743        // quoted field over two lines: the header the chunker found held a
1744        // record, which every later chunk repeated, and its cut fell inside
1745        // the quoted field.
1746        let more = format!("{MISPLACED_QUOTES}\nG,H,I\nJ\"K,L,\"M\nN\"\n");
1747        let want = csv_streams_as_whole(&more, &header).unwrap();
1748        assert_eq!(objects(&want), 3, "{want:?}");
1749        csv_streams_as_whole(&more, &corpus).unwrap();
1750    }
1751
1752    #[test]
1753    fn an_input_ending_inside_a_quoted_field_fails_as_the_grammar_does() {
1754        // In the header, which a header-only chunk never parsed, and in a
1755        // record, with and without a newline after it.
1756        for text in [
1757            "a,\"b\n",
1758            "a,\"b",
1759            "a,\"b\nc,d\n",
1760            "\"\n",
1761            "a\n\"x\n",
1762            "a\n1\n\"x",
1763        ] {
1764            let want = csv_streams_as_whole(text, &CsvOptions::default());
1765            assert!(
1766                matches!(&want, Err((code, ..)) if code == "unterminated_string"),
1767                "{text:?}: {want:?}"
1768            );
1769        }
1770    }
1771
1772    #[test]
1773    fn a_last_record_without_a_newline_is_read() {
1774        for text in ["a,b\n1,2\n3,4", "a,b\n1,2\n3,\"x\ny\"", "a,b\r\n1,2\r\n3,4"] {
1775            let want = csv_streams_as_whole(text, &CsvOptions::default()).unwrap();
1776            assert_eq!(objects(&want), 2, "{text:?}");
1777        }
1778    }
1779
1780    fn csv_options(edit: impl FnOnce(&mut CsvOptions)) -> CsvOptions {
1781        let mut options = CsvOptions::default();
1782        edit(&mut options);
1783        options
1784    }
1785
1786    #[test]
1787    fn the_header_is_the_first_record_the_grammar_reads() {
1788        let empty = csv_options(|o| o.record.empty = true);
1789        let relaxed = csv_options(|o| o.strict = false);
1790        let comment = csv_options(|o| o.comment = Some(true));
1791        let cases: &[(&str, &CsvOptions, usize)] = &[
1792            // A blank line before it is none of it...
1793            ("\n\na,b\n1,2\n3,4\n", &CsvOptions::default(), 2),
1794            ("\r\na,b\r\n1,2\r\n3,4\r\n", &CsvOptions::default(), 2),
1795            // ...unless a blank line is a record, when it is the header.
1796            ("\na,b\n\n1,2\n", &empty, 3),
1797            // A line of spaces is the header in strict mode, blank otherwise.
1798            ("  \na,b\n1,2\n3,4\n", &CsvOptions::default(), 3),
1799            ("  \na,b\n1,2\n3,4\n", &relaxed, 2),
1800            // A comment is no record, and a quote inside one no quote.
1801            ("# x \"\na,b\n1,2\n3,4\n", &comment, 2),
1802            ("// x \"\na,b\n1,2\n3,4\n", &relaxed, 2),
1803            ("a,b # c \"\n1,2\n3,4\n", &comment, 2),
1804        ];
1805        for (text, options, records) in cases {
1806            let want = csv_streams_as_whole(text, options).unwrap();
1807            assert_eq!(objects(&want), *records, "{text:?}");
1808        }
1809    }
1810
1811    #[test]
1812    fn a_lone_carriage_return_ends_a_csv_record() {
1813        for text in ["a,b\r1,2\r3,4\r", "a,b\r1,2\n3,4\n5,6\n"] {
1814            let want = csv_streams_as_whole(text, &CsvOptions::default()).unwrap();
1815            assert!(objects(&want) >= 2, "{text:?}");
1816        }
1817    }
1818
1819    #[test]
1820    fn a_field_spans_lines_only_where_the_grammar_reads_one_that_does() {
1821        let quote = csv_options(|o| o.string.quote = "'".into());
1822        let tildes = csv_options(|o| o.field.separation = Some("~~".into()));
1823        let comment = csv_options(|o| o.comment = Some(true));
1824        let cases: &[(&str, &CsvOptions)] = &[
1825            // The engine's own strings: a backtick spans lines, and an
1826            // escaped newline continues a single-quoted one, which a `"`
1827            // inside does not end.
1828            ("a,b\n`x\ny`,z\n1,2\n", &CsvOptions::default()),
1829            ("a,b\n'x\\\ny',z\n1,2\n", &CsvOptions::default()),
1830            ("a,b\n'x,\"y',z\n\"p\nq\",r\n", &CsvOptions::default()),
1831            // A quote after a space, or after a separator of the grammar's.
1832            ("a,b\nx, \"p\nq\"\n1,2\n", &CsvOptions::default()),
1833            ("a~~b\nx~~\"p\nq\"\n1~~2\n", &tildes),
1834            // The configured quote, and a `"` that is then the engine's,
1835            // which a line refuses.
1836            ("a,b\n'x\ny',z\n1,2\n", &quote),
1837            ("a,b\n\"x,y\n1,2\n", &quote),
1838            // A block comment over lines.
1839            ("a,b\n/* x\ny */1,2\n3,4\n", &comment),
1840        ];
1841        for (text, options) in cases {
1842            let _ = csv_streams_as_whole(text, options);
1843        }
1844    }
1845
1846    #[test]
1847    fn configured_record_separators_end_records() {
1848        // A newline is then field text, and never a place to cut.
1849        let semicolons = csv_options(|o| o.record.separators = Some(";".into()));
1850        let want = csv_streams_as_whole("a,b;1,x\ny;3,4\nz;5,6", &semicolons).unwrap();
1851        assert_eq!(objects(&want), 3);
1852    }
1853
1854    #[test]
1855    fn a_jsonl_line_is_blank_only_when_the_grammar_reads_it_so() {
1856        // Space and tab are the grammar's blanks; a form feed, a vertical
1857        // tab, a no-break space or a line separator is not, and the
1858        // grammar refuses the line rather than skipping it.
1859        for blank in ["\u{c}", "\u{b}", "\u{a0}", "\u{85}", "\u{2028}", "\u{3000}"] {
1860            let text = format!("{{\"a\":1}}\n{blank}\n{{\"b\":2}}\n");
1861            let want = jsonl_streams_as_whole(&text);
1862            assert_eq!(
1863                want.unwrap_err().1,
1864                Some(2),
1865                "{blank:?} is refused on its line"
1866            );
1867        }
1868        let want = jsonl_streams_as_whole("{\"a\":1}\n \t\n\r\n{\"b\":2}\n   ").unwrap();
1869        assert_eq!(objects(&want), 2);
1870    }
1871
1872    #[test]
1873    fn a_lone_carriage_return_ends_a_jsonl_record() {
1874        let want = jsonl_streams_as_whole("{\"a\":1}\r{\"b\":2}\r\r \r{\"c\":3}\n").unwrap();
1875        assert_eq!(objects(&want), 3);
1876        // A value cut by one is incomplete, as the grammar reads it, and
1877        // the position of a failure after one is the grammar's.
1878        for text in ["{\"a\":\r1}\n", "{\"a\":1}\r{\"b\": }\n"] {
1879            jsonl_streams_as_whole(text).unwrap_err();
1880        }
1881        // Inside a string it is the string's (refused there, as a control
1882        // character), not a record's end.
1883        jsonl_streams_as_whole("{\"a\":\"x\ry\"}\n").unwrap_err();
1884    }
1885
1886    #[test]
1887    fn records_one_line_holds_are_bounded_and_cut_one_by_one() {
1888        // Many records and no `\n`: records ended by a lone `\r`, by a
1889        // configured separator, and JSON Lines records ended by `\r`. Each
1890        // record is under the limit and the line far over it, so the limit
1891        // is the record's, and a chunk closes after a record rather than
1892        // after the line.
1893        let limits = Limits {
1894            max_record_bytes: 8,
1895            ..Limits::default()
1896        };
1897        let crs = format!("a,b\r{}", "1,2\r".repeat(200));
1898        let semicolons = csv_options(|o| o.record.separators = Some(";".into()));
1899        let separated = format!("a,b;{}", "1,x;".repeat(200));
1900        for (text, options) in [(&crs, CsvOptions::default()), (&separated, semicolons)] {
1901            let want = whole(tabnas_csv::make_with(options.clone()).parse(text));
1902            assert_eq!(objects(want.as_ref().unwrap()), 200, "{text:?}");
1903            let format = LineFormat::csv_with(options.header, options.clone());
1904            for chunk_bytes in [0, 64, DEFAULT_CHUNK_BYTES] {
1905                let mut rec: Vec<OwnedJsonEvent> = Vec::new();
1906                let outcome = LinesSource::new(Cursor::new(text.as_str()), format.clone())
1907                    .limits(limits.clone())
1908                    .chunk_bytes(chunk_bytes)
1909                    .run(&mut rec);
1910                assert_eq!(reading(outcome, rec), want, "{text:?}, chunk {chunk_bytes}");
1911            }
1912            // A chunk holds at most its size and one record more.
1913            let parser = tabnas_csv::make_with(options.clone());
1914            let mut chunks = Chunks::new(
1915                Cursor::new(text.as_bytes()),
1916                &parser,
1917                &options,
1918                64,
1919                limits.max_record_bytes,
1920            );
1921            let mut count = 0;
1922            while let Some(chunk) = chunks.next_chunk().unwrap() {
1923                assert!(chunk.text.len() <= 64 + 8, "{:?}", chunk.text);
1924                count += 1;
1925            }
1926            assert!(count > 10, "{text:?}: {count} chunks");
1927        }
1928        let records = "{\"a\":1}\r".repeat(200);
1929        let want = whole(tabnas_jsonl::parse(&records));
1930        assert_eq!(objects(want.as_ref().unwrap()), 200);
1931        let mut rec: Vec<OwnedJsonEvent> = Vec::new();
1932        let outcome = LinesSource::new(Cursor::new(records.as_str()), LineFormat::Jsonl)
1933            .limits(limits.clone())
1934            .run(&mut rec);
1935        assert_eq!(reading(outcome, rec), want);
1936        let (outcome, rec) = LinesSource::new(Cursor::new(records.as_str()), LineFormat::Jsonl)
1937            .limits(limits.clone())
1938            .run_owned(Vec::<OwnedJsonEvent>::new());
1939        assert_eq!(reading(outcome, without_lexemes(&rec)), want);
1940        // A record over the limit still fails, at the row it starts on,
1941        // which is counted as the engine counts rows: by `\n`, or by the
1942        // configured separator.
1943        let semicolons = csv_options(|o| o.record.separators = Some(";".into()));
1944        for (text, options, row) in [
1945            ("a,b\r1,2\r123456789,x\r", CsvOptions::default(), 1),
1946            ("a,b;1,x;123456789,x;", semicolons, 3),
1947        ] {
1948            let err = LinesSource::new(
1949                Cursor::new(text),
1950                LineFormat::csv_with(options.header, options),
1951            )
1952            .limits(limits.clone())
1953            .run(&mut Vec::<OwnedJsonEvent>::new())
1954            .unwrap_err();
1955            assert_eq!(err.limit.as_ref().unwrap().name, "max_record_bytes");
1956            assert_eq!(err.row, Some(row), "{text:?}: {err}");
1957        }
1958    }
1959
1960    #[test]
1961    fn a_line_token_of_two_characters_is_never_cut() {
1962        // Under `record.empty` a run of line characters ends at a repeated
1963        // one, so `\r\n`, and `\n\r` as much, is one line token. A chunk
1964        // cut between its two characters would start with a line token,
1965        // a record of its own to a chunk without a header.
1966        for header in [false, true] {
1967            let options = csv_options(|o| {
1968                o.record.empty = true;
1969                o.header = header;
1970            });
1971            for text in [
1972                "a,b\r\n1,2\r\n\r\n3,4\r\n",
1973                "a,b\n\r1,2\n\r\n\r3,4",
1974                "a\r\n\r\r\nb\r\n",
1975            ] {
1976                csv_streams_as_whole(text, &options).unwrap();
1977            }
1978        }
1979    }
1980
1981    #[test]
1982    fn a_line_character_inside_a_jsonl_string_is_the_strings() {
1983        // The grammar refuses a string at a raw line character, and the
1984        // record is read whole up to the line character after it, so the
1985        // failure is the grammar's: a `\n` in a string ends a record no
1986        // more than a `\r` does.
1987        for text in [
1988            "{\"a\":\"x\ny\"}\n{\"b\":1}\n",
1989            "[\"\\\n\"]\n",
1990            "{\"a\":\"x\r\ny\"}\r\n",
1991        ] {
1992            jsonl_streams_as_whole(text).unwrap_err();
1993        }
1994    }
1995
1996    #[test]
1997    fn a_line_character_inside_a_fixed_token_ends_no_piece() {
1998        // The lexer reads a fixed token before a line, so a field separator
1999        // that holds a line character owns it, and a piece does not end
2000        // there. Where a fixed token holds one, a run of line characters is
2001        // one token to the lexer, and a piece takes the run whole, so that
2002        // no chunk starts inside it.
2003        let starts = csv_options(|o| o.field.separation = Some("\n~".into()));
2004        let inside = csv_options(|o| o.field.separation = Some("~\n~".into()));
2005        for (text, options) in [
2006            ("a\n~b\nx\n~y\n", &starts),
2007            ("a\n~b\n\n~c\nx\n~y\n", &starts),
2008            ("a\n~b\r\n~c\nx\n~y", &starts),
2009            ("a~\n~b\nx~\n~y\n", &inside),
2010        ] {
2011            let _ = csv_streams_as_whole(text, options);
2012        }
2013    }
2014
2015    #[test]
2016    fn a_long_line_token_is_followed_once() {
2017        // Where a fixed token holds a line character, a piece takes a whole
2018        // run of line characters, which it follows across reads from where
2019        // it stopped: read a byte at a time, a run costs its length and not
2020        // its square. Rows count every `\n`, the fixed token's included, as
2021        // the engine counts them.
2022        let run = 100_000;
2023        let text = format!("a\n~b\nc\n~d{}x\n~y\n", "\n".repeat(run));
2024        let mut pieces = Pieces::new(
2025            trickle(&text, 1),
2026            vec!['\r', '\n'],
2027            vec!['\n'],
2028            false,
2029            &["\n~".to_string()],
2030        );
2031        let mut got = Vec::new();
2032        while let Some((row, piece)) = pieces.next_piece(usize::MAX, || unreachable!()).unwrap() {
2033            got.push((row, piece.len()));
2034        }
2035        let run = run as u64;
2036        assert_eq!(got, [(1, 5), (3, 4 + run as usize), (4 + run, 5)]);
2037    }
2038
2039    #[test]
2040    fn a_separator_of_several_bytes_is_found_across_reads() {
2041        // A configured separator outside ASCII is matched on its whole
2042        // UTF-8 form, read a byte at a time too; under `record.empty` a
2043        // token of two of them is followed across reads, and a character
2044        // that only starts like one is left to the next record.
2045        let one = csv_options(|o| o.record.separators = Some("␞".into()));
2046        csv_streams_as_whole("a,b␞1,é␞3,4␞", &one).unwrap();
2047        // Outside the basic plane too, and a failure after one is placed
2048        // as the grammar places it.
2049        let astral = csv_options(|o| o.record.separators = Some("😀".into()));
2050        csv_streams_as_whole("a,b😀1,é😀3,4😀", &astral).unwrap();
2051        csv_streams_as_whole("a,b😀1,2😀3,\"x", &astral).unwrap_err();
2052        let two = csv_options(|o| {
2053            o.record.separators = Some("␞¶".into());
2054            o.record.empty = true;
2055            o.header = false;
2056        });
2057        csv_streams_as_whole("a,b␞¶£,é␞£,2¶␞3,4", &two).unwrap();
2058    }
2059}