1use std::borrow::Cow;
31
32use rudb_common::{Error, Result};
33
34use crate::dialect::Dialect;
35
36pub fn record(
48 bytes: &[u8],
49 from: usize,
50 dialect: Dialect,
51 eof: bool,
52 out: &mut Vec<String>,
53) -> Result<Option<usize>> {
54 if from >= bytes.len() {
55 return Ok(None);
60 }
61 let quote = dialect.quote_byte();
62 let escape = dialect.escape_byte();
63 let mut at = from;
64 let mut count = 0;
65 let mut field = Vec::new();
66 loop {
67 field.clear();
68 if bytes.get(at) == Some("e) {
69 at += 1;
70 loop {
71 let Some(&byte) = bytes.get(at) else { return Ok(None) };
72 if byte == escape && bytes.get(at + 1) == Some("e) {
73 field.push(quote);
74 at += 2;
75 continue;
76 }
77 if byte == quote {
78 at += 1;
79 break;
80 }
81 field.push(byte);
82 at += 1;
83 }
84 match bytes.get(at) {
85 None if !eof => return Ok(None),
86 None => {}
87 Some(&byte) if byte == dialect.delimiter || byte == b'\n' || byte == b'\r' => {}
88 Some(&byte) => return Err(after_quote(byte)),
89 }
90 } else {
91 while let Some(&byte) = bytes.get(at) {
92 if byte == dialect.delimiter || byte == b'\n' || byte == b'\r' {
93 break;
94 }
95 field.push(byte);
96 at += 1;
97 }
98 if at >= bytes.len() && !eof {
99 return Ok(None);
100 }
101 }
102 place(out, count, &field);
103 count += 1;
104 match bytes.get(at) {
105 Some(&byte) if byte == dialect.delimiter => at += 1,
106 Some(b'\r') => {
107 at += 1;
108 if bytes.get(at) == Some(&b'\n') {
109 at += 1;
110 } else if at >= bytes.len() && !eof {
111 return Ok(None);
114 }
115 break;
116 }
117 Some(b'\n') => {
118 at += 1;
119 break;
120 }
121 Some(_) => unreachable!("a field stops at a delimiter, a line ending or the end"),
122 None => break,
123 }
124 }
125 out.truncate(count);
126 Ok(Some(at))
127}
128
129fn place(out: &mut Vec<String>, at: usize, field: &[u8]) {
136 let text = String::from_utf8_lossy(field);
137 match out.get_mut(at) {
138 Some(held) => {
139 held.clear();
140 held.push_str(&text);
141 }
142 None => out.push(text.into_owned()),
143 }
144}
145
146fn after_quote(byte: u8) -> Error {
149 Error::io(format!(
150 "a quoted value is followed by '{}' rather than by a delimiter or the end of the line",
151 byte as char
152 ))
153}
154
155#[derive(Debug, Clone, Copy, PartialEq, Eq)]
165pub struct Span {
166 start: u32,
167 end: u32,
168}
169
170const ESCAPED: u32 = 1 << 31;
172
173impl Span {
174 pub const MOST: usize = (ESCAPED - 1) as usize;
176
177 #[must_use]
183 pub fn new(start: usize, end: usize, escaped: bool) -> Self {
184 debug_assert!(start <= end && end <= Self::MOST);
185 #[allow(clippy::cast_possible_truncation)]
186 let (start, end) = (start as u32, end as u32);
187 Self { start, end: end | if escaped { ESCAPED } else { 0 } }
188 }
189
190 #[must_use]
192 pub const fn start(self) -> usize {
193 self.start as usize
194 }
195
196 #[must_use]
198 pub const fn end(self) -> usize {
199 (self.end & !ESCAPED) as usize
200 }
201
202 #[must_use]
204 pub const fn escaped(self) -> bool {
205 self.end & ESCAPED != 0
206 }
207
208 #[must_use]
213 pub const fn is_empty(self) -> bool {
214 self.start() == self.end()
215 }
216
217 #[must_use]
219 pub const fn len(self) -> usize {
220 self.end() - self.start()
221 }
222
223 #[must_use]
225 pub fn raw(self, bytes: &[u8]) -> &[u8] {
226 &bytes[self.start()..self.end()]
227 }
228
229 #[must_use]
236 pub fn text(self, bytes: &[u8], dialect: Dialect) -> Cow<'_, str> {
237 let raw = self.raw(bytes);
238 if !self.escaped() {
239 return String::from_utf8_lossy(raw);
240 }
241 Cow::Owned(String::from_utf8_lossy(&unescape(raw, dialect)).into_owned())
242 }
243}
244
245fn unescape(raw: &[u8], dialect: Dialect) -> Vec<u8> {
252 let quote = dialect.quote_byte();
253 let escape = dialect.escape_byte();
254 let mut out = Vec::with_capacity(raw.len());
255 let mut at = 0;
256 while let Some(&byte) = raw.get(at) {
257 if byte == escape && raw.get(at + 1) == Some("e) {
258 out.push(quote);
259 at += 2;
260 } else {
261 out.push(byte);
262 at += 1;
263 }
264 }
265 out
266}
267
268#[derive(Debug, Clone, Default)]
273pub struct Records {
274 spans: Vec<Span>,
275 ends: Vec<usize>,
276}
277
278impl Records {
279 pub fn clear(&mut self) {
281 self.spans.clear();
282 self.ends.clear();
283 }
284
285 #[must_use]
287 pub fn len(&self) -> usize {
288 self.ends.len()
289 }
290
291 #[must_use]
293 pub fn is_empty(&self) -> bool {
294 self.ends.is_empty()
295 }
296
297 #[must_use]
303 pub fn fields(&self, at: usize) -> &[Span] {
304 let start = if at == 0 { 0 } else { self.ends[at - 1] };
305 &self.spans[start..self.ends[at]]
306 }
307
308 #[must_use]
310 pub fn field(&self, row: usize, column: usize) -> Option<Span> {
311 let start = if row == 0 { 0 } else { self.ends[row - 1] };
312 let at = start + column;
313 if at < self.ends[row] { Some(self.spans[at]) } else { None }
314 }
315
316 pub fn shift(&mut self, by: usize) {
319 if by == 0 {
320 return;
321 }
322 #[allow(clippy::cast_possible_truncation)]
324 let by = by as u32;
325 for span in &mut self.spans {
326 span.start -= by;
327 span.end -= by;
328 }
329 }
330}
331
332pub fn records(
345 bytes: &[u8],
346 from: usize,
347 dialect: Dialect,
348 eof: bool,
349 limit: usize,
350 out: &mut Records,
351) -> Result<usize> {
352 let quote = dialect.quote_byte();
353 let delimiter = dialect.delimiter;
354 let structural = |byte: u8| byte == quote || byte == b'\n' || byte == b'\r';
355 if dialect.escape_byte() == quote && !structural(delimiter) {
356 blocks(bytes, from, dialect, eof, limit, out)
357 } else {
358 let mut at = from;
359 while out.len() < limit {
360 let Some(next) = spans(bytes, at, dialect, eof, &mut out.spans)? else { break };
361 out.ends.push(out.spans.len());
362 at = next;
363 }
364 Ok(at)
365 }
366}
367
368fn blocks(
382 bytes: &[u8],
383 from: usize,
384 dialect: Dialect,
385 eof: bool,
386 limit: usize,
387 out: &mut Records,
388) -> Result<usize> {
389 let delimiter = dialect.delimiter;
390 let quote = dialect.quote_byte();
391 let len = bytes.len();
392 let mut record = from;
393 loop {
394 let mut mark = out.spans.len();
397 let mut field = record;
398 let mut quoted = false;
399 let mut inside = 0u64;
400 let mut block = record;
401 'blocks: while block < len && out.len() < limit {
402 let end = len.min(block + 64);
403 let (quotes, ends) = masks(&bytes[block..end], delimiter, quote);
404 let prefix = prefix_xor(quotes) ^ inside;
405 inside = 0u64.wrapping_sub(prefix >> 63);
406 let mut structural = ends & !prefix;
407 while structural != 0 {
408 let bit = structural.trailing_zeros() as usize;
409 structural &= structural - 1;
410 let at = block + bit;
411 if at < field {
412 continue;
414 }
415 let low = field.saturating_sub(block);
416 if quoted || quotes & below(bit) & !below(low) != 0 {
417 let Some(escaped) = enclosed(&bytes[field..at], quote) else {
418 break 'blocks;
419 };
420 out.spans.push(Span::new(field + 1, at - 1, escaped));
421 quoted = false;
422 } else {
423 out.spans.push(Span::new(field, at, false));
424 }
425 let byte = bytes[at];
426 if byte == delimiter {
427 field = at + 1;
428 continue;
429 }
430 let mut next = at + 1;
431 if byte == b'\r' {
432 match bytes.get(next) {
433 Some(b'\n') => next += 1,
434 Some(_) => {}
435 None if eof => {}
436 None => {
439 out.spans.truncate(mark);
440 return Ok(record);
441 }
442 }
443 }
444 out.ends.push(out.spans.len());
445 mark = out.spans.len();
446 record = next;
447 field = next;
448 if out.len() == limit {
449 return Ok(record);
450 }
451 }
452 if field < end && quotes >> field.saturating_sub(block) != 0 {
453 quoted = true;
454 }
455 block = end;
456 }
457 if out.len() >= limit || record >= len {
458 return Ok(record);
459 }
460 out.spans.truncate(mark);
464 let Some(next) = spans(bytes, record, dialect, eof, &mut out.spans)? else {
465 return Ok(record);
466 };
467 out.ends.push(out.spans.len());
468 record = next;
469 }
470}
471
472#[inline]
480fn masks(block: &[u8], delimiter: u8, quote: u8) -> (u64, u64) {
481 let mut padded = [0u8; 64];
482 let full: &[u8; 64] = if let Ok(full) = block.try_into() {
483 full
484 } else {
485 padded[..block.len()].copy_from_slice(block);
486 &padded
487 };
488 let mut class = [0u8; 64];
492 for (class, &byte) in class.iter_mut().zip(full) {
493 let end = (byte == delimiter) | (byte == b'\n') | (byte == b'\r');
494 *class = u8::from(byte == quote) | (u8::from(end) << 1);
495 }
496 let mut quotes = 0u64;
497 let mut ends = 0u64;
498 for (at, eight) in class.chunks_exact(8).enumerate() {
499 let word = u64::from_le_bytes(eight.try_into().expect("eight bytes"));
500 quotes |= gather(word & LOW_BITS) << (8 * at);
501 ends |= gather((word >> 1) & LOW_BITS) << (8 * at);
502 }
503 if block.len() < 64 {
504 let live = below(block.len());
505 quotes &= live;
506 ends &= live;
507 }
508 (quotes, ends)
509}
510
511const LOW_BITS: u64 = 0x0101_0101_0101_0101;
513
514const fn gather(word: u64) -> u64 {
519 word.wrapping_mul(0x0102_0408_1020_4080) >> 56
520}
521
522const fn prefix_xor(mut bits: u64) -> u64 {
524 bits ^= bits << 1;
525 bits ^= bits << 2;
526 bits ^= bits << 4;
527 bits ^= bits << 8;
528 bits ^= bits << 16;
529 bits ^= bits << 32;
530 bits
531}
532
533const fn below(count: usize) -> u64 {
535 (1u64 << count) - 1
536}
537
538fn enclosed(field: &[u8], quote: u8) -> Option<bool> {
545 let [first, inner @ .., last] = field else { return None };
546 if *first != quote || *last != quote {
547 return None;
548 }
549 let mut escaped = false;
550 let mut at = 0;
551 while let Some(found) = inner[at..].iter().position(|&byte| byte == quote) {
552 if inner.get(at + found + 1) != Some("e) {
553 return None;
554 }
555 escaped = true;
556 at += found + 2;
557 }
558 Some(escaped)
559}
560
561fn spans(
566 bytes: &[u8],
567 from: usize,
568 dialect: Dialect,
569 eof: bool,
570 out: &mut Vec<Span>,
571) -> Result<Option<usize>> {
572 if from >= bytes.len() {
573 return Ok(None);
574 }
575 let mark = out.len();
576 let quote = dialect.quote_byte();
577 let escape = dialect.escape_byte();
578 let mut at = from;
579 loop {
580 let span;
581 if bytes.get(at) == Some("e) {
582 at += 1;
583 let start = at;
584 let mut escaped = false;
585 loop {
586 let Some(&byte) = bytes.get(at) else {
587 out.truncate(mark);
588 return Ok(None);
589 };
590 if byte == escape && bytes.get(at + 1) == Some("e) {
591 escaped = true;
592 at += 2;
593 continue;
594 }
595 if byte == quote {
596 break;
597 }
598 at += 1;
599 }
600 span = Span::new(start, at, escaped);
601 at += 1;
602 match bytes.get(at) {
603 None if !eof => {
604 out.truncate(mark);
605 return Ok(None);
606 }
607 None => {}
608 Some(&byte) if byte == dialect.delimiter || byte == b'\n' || byte == b'\r' => {}
609 Some(&byte) => {
610 out.truncate(mark);
611 return Err(after_quote(byte));
612 }
613 }
614 } else {
615 let start = at;
616 while let Some(&byte) = bytes.get(at) {
617 if byte == dialect.delimiter || byte == b'\n' || byte == b'\r' {
618 break;
619 }
620 at += 1;
621 }
622 if at >= bytes.len() && !eof {
623 out.truncate(mark);
624 return Ok(None);
625 }
626 span = Span::new(start, at, false);
627 }
628 out.push(span);
629 match bytes.get(at) {
630 Some(&byte) if byte == dialect.delimiter => at += 1,
631 Some(b'\r') => {
632 at += 1;
633 if bytes.get(at) == Some(&b'\n') {
634 at += 1;
635 } else if at >= bytes.len() && !eof {
636 out.truncate(mark);
637 return Ok(None);
638 }
639 break;
640 }
641 Some(b'\n') => {
642 at += 1;
643 break;
644 }
645 Some(_) => unreachable!("a field stops at a delimiter, a line ending or the end"),
646 None => break,
647 }
648 }
649 Ok(Some(at))
650}
651
652#[cfg(test)]
653mod tests {
654 use super::*;
655
656 fn split(bytes: &[u8], dialect: Dialect) -> Vec<Vec<String>> {
657 let mut rows = Vec::new();
658 let mut fields = Vec::new();
659 let mut at = 0;
660 while at < bytes.len() {
661 let next = record(bytes, at, dialect, true, &mut fields)
662 .expect("splits")
663 .expect("a whole record");
664 rows.push(fields.clone());
665 at = next;
666 }
667 rows
668 }
669
670 fn comma() -> Dialect {
671 Dialect { delimiter: b',', quote: Some(b'"'), escape: Some(b'"'), header: true }
672 }
673
674 #[test]
675 fn a_line_of_fields_is_the_fields_of_that_line() {
676 assert_eq!(split(b"a,b,c\n1,2,3\n", comma()), [["a", "b", "c"], ["1", "2", "3"]]);
677 }
678
679 #[test]
680 fn the_last_line_does_not_need_a_newline_on_it() {
681 assert_eq!(split(b"a,b\n1,2", comma()), [["a", "b"], ["1", "2"]]);
682 }
683
684 #[test]
685 fn all_three_line_endings_end_a_line() {
686 assert_eq!(split(b"a\r\nb\rc\n", comma()), [["a"], ["b"], ["c"]]);
687 }
688
689 #[test]
690 fn a_quoted_field_may_hold_the_delimiter_and_a_newline() {
691 assert_eq!(split(b"1,\"x,y\"\n", comma()), [["1", "x,y"]]);
692 assert_eq!(split(b"1,\"x\ny\"\n", comma()), [["1", "x\ny"]]);
693 }
694
695 #[test]
696 fn a_doubled_quote_inside_a_quoted_field_is_one_quote() {
697 assert_eq!(split(b"1,\"say \"\"hi\"\"\"\n", comma()), [["1", "say \"hi\""]]);
698 }
699
700 #[test]
701 fn an_empty_field_is_an_empty_string_here_and_becomes_a_null_above() {
702 assert_eq!(split(b"1,,3\n", comma()), [["1", "", "3"]]);
703 assert_eq!(split(b"1,\"\",3\n", comma()), [["1", "", "3"]]);
704 }
705
706 #[test]
707 fn a_trailing_delimiter_makes_a_last_empty_field() {
708 assert_eq!(split(b"1|x|\n", Dialect { delimiter: b'|', ..comma() }), [["1", "x", ""]]);
709 }
710
711 #[test]
712 fn a_quote_in_the_middle_of_a_bare_field_is_just_a_character() {
713 assert_eq!(split(b"1,he said \"hi\"\n", comma()), [["1", "he said \"hi\""]]);
714 }
715
716 #[test]
717 fn a_record_that_the_buffer_does_not_hold_all_of_is_not_a_record_yet() {
718 let mut fields = Vec::new();
719 assert_eq!(record(b"a,b", 0, comma(), false, &mut fields).unwrap(), None);
720 assert_eq!(record(b"a,\"b", 0, comma(), true, &mut fields).unwrap(), None);
721 assert_eq!(record(b"a,b\n", 0, comma(), false, &mut fields).unwrap(), Some(4));
722 }
723
724 #[test]
725 fn rubbish_after_a_closing_quote_is_an_error_rather_than_a_guess() {
726 let mut fields = Vec::new();
727 let error = record(b"\"x\"y,2\n", 0, comma(), true, &mut fields).unwrap_err();
728 assert!(error.message().contains("quoted value"), "{error}");
729 }
730
731 #[test]
732 fn utf8_survives_being_read_one_byte_at_a_time() {
733 assert_eq!(split("a,héllo\n".as_bytes(), comma()), [["a", "héllo"]]);
734 }
735}
736
737#[cfg(test)]
743mod agree {
744 use super::*;
745
746 type Outcome = (Vec<Vec<String>>, usize, Option<String>);
748
749 fn by_record(bytes: &[u8], from: usize, dialect: Dialect, eof: bool) -> Outcome {
750 let mut rows = Vec::new();
751 let mut fields = Vec::new();
752 let mut at = from;
753 loop {
754 match record(bytes, at, dialect, eof, &mut fields) {
755 Ok(Some(next)) => {
756 rows.push(fields.clone());
757 at = next;
758 }
759 Ok(None) => return (rows, at, None),
760 Err(error) => return (rows, at, Some(error.to_string())),
761 }
762 }
763 }
764
765 fn by_chunk(bytes: &[u8], from: usize, dialect: Dialect, eof: bool, limit: usize) -> Outcome {
766 let mut rows = Vec::new();
767 let mut at = from;
768 let mut out = Records::default();
769 loop {
770 out.clear();
771 let result = records(bytes, at, dialect, eof, limit, &mut out);
772 for row in 0..out.len() {
773 rows.push(
774 out.fields(row)
775 .iter()
776 .map(|span| span.text(bytes, dialect).into_owned())
777 .collect(),
778 );
779 }
780 match result {
781 Ok(next) => {
782 at = next;
783 if out.len() < limit {
784 return (rows, at, None);
785 }
786 }
787 Err(error) => {
788 let mut from = at;
791 let mut fields = Vec::new();
792 for _ in 0..out.len() {
793 from = record(bytes, from, dialect, eof, &mut fields).unwrap().unwrap();
794 }
795 return (rows, from, Some(error.to_string()));
796 }
797 }
798 }
799 }
800
801 fn check(bytes: &[u8], dialect: Dialect, cuts: &[usize]) {
804 for eof in [true, false] {
805 let expected = by_record(bytes, 0, dialect, eof);
806 for limit in [1, 2, 3, 7, 8192] {
807 assert_eq!(
808 by_chunk(bytes, 0, dialect, eof, limit),
809 expected,
810 "{:?} under {dialect:?}, eof {eof}, {limit} at a time",
811 String::from_utf8_lossy(bytes),
812 );
813 }
814 }
815 let whole = by_record(bytes, 0, dialect, true);
816 for &cut in cuts {
817 let cut = cut.min(bytes.len());
818 let (mut rows, at, error) = by_chunk(&bytes[..cut], 0, dialect, false, 8192);
821 if error.is_some() {
822 assert_eq!(error, whole.2, "an error in the front is the error in the whole");
823 continue;
824 }
825 let (rest, end, error) = by_chunk(bytes, at, dialect, true, 8192);
826 rows.extend(rest);
827 assert_eq!(
828 (rows, end, error),
829 whole,
830 "{:?} under {dialect:?} cut at {cut}",
831 String::from_utf8_lossy(bytes),
832 );
833 }
834 }
835
836 fn dialects() -> [Dialect; 5] {
837 let comma =
838 Dialect { delimiter: b',', quote: Some(b'"'), escape: Some(b'"'), header: false };
839 [
840 comma,
841 Dialect { quote: None, escape: None, ..comma },
842 Dialect { delimiter: b'|', ..comma },
843 Dialect { delimiter: b'\t', quote: Some(b'\''), escape: Some(b'\''), ..comma },
844 Dialect { escape: Some(b'\\'), ..comma },
845 ]
846 }
847
848 #[test]
849 fn the_tricky_ones_split_the_same_both_ways() {
850 let long = "x".repeat(61);
851 let cases: Vec<Vec<u8>> = vec![
852 b"".to_vec(),
853 b"\n".to_vec(),
854 b"\n\n".to_vec(),
855 b"a".to_vec(),
856 b"a,b,c\n1,2,3\n".to_vec(),
857 b"a,b\n1,2".to_vec(),
858 b"a\r\nb\rc\n".to_vec(),
859 b"a\r".to_vec(),
860 b"a\r\r\n\n".to_vec(),
861 b"1,\"x,y\"\n2,\"x\ny\"\n".to_vec(),
862 b"1,\"say \"\"hi\"\"\"\n".to_vec(),
863 b"1,,3\n1,\"\",3\n".to_vec(),
864 b"1|x|\n1,x,\n".to_vec(),
865 b"1,he said \"hi\"\n2,x\n".to_vec(),
866 b"1,he said \"hi\n2,x\n".to_vec(),
867 b"\"x\"y,2\n".to_vec(),
868 b"\"x\"\"\n".to_vec(),
869 b"\"x\"\"".to_vec(),
870 b"a,\"b".to_vec(),
871 b"a,\"b\"".to_vec(),
872 b"\"".to_vec(),
873 b"\"\"\"\"\n".to_vec(),
874 b"\"a\"\"\",b\n".to_vec(),
875 b"\"a\\\"b\",c\n\"a\\\\\"\n".to_vec(),
876 b"'a,b'\t'c''d'\n".to_vec(),
877 b"x\xffy,\"\xfe\"\"\"\n".to_vec(),
878 "h\u{e9}llo,w\u{f6}rld\n".as_bytes().to_vec(),
879 format!("{long},\"a\nb\",c\n{long}\r\n\"{long}\"\"{long}\",d\n").into_bytes(),
880 format!("{long}ab\r\n{long}abc\r\n").into_bytes(),
881 format!("\"{long}\"\"\",\"\n\"\n{long},x\"\n").into_bytes(),
882 ];
883 for bytes in &cases {
884 let cuts: Vec<usize> = (0..=bytes.len()).collect();
885 for dialect in dialects() {
886 check(bytes, dialect, &cuts);
887 }
888 }
889 }
890
891 struct Rng(u64);
893
894 impl Rng {
895 fn next(&mut self) -> u64 {
896 self.0 ^= self.0 << 13;
897 self.0 ^= self.0 >> 7;
898 self.0 ^= self.0 << 17;
899 self.0
900 }
901
902 fn below(&mut self, n: usize) -> usize {
903 (self.next() % n as u64) as usize
904 }
905 }
906
907 fn generate(rng: &mut Rng) -> Vec<u8> {
909 const PIECES: [&[u8]; 20] = [
910 b"a",
911 b"1",
912 b"-2.5",
913 b"xyz",
914 b",",
915 b"|",
916 b"\t",
917 b"\"",
918 b"\"\"",
919 b"'",
920 b"\\",
921 b"\n",
922 b"\r",
923 b"\r\n",
924 b" ",
925 b"\xc3\xa9",
926 b"\xff",
927 b"2020-01-02",
928 b"",
929 b"0123456789abcdef",
930 ];
931 let mut out = Vec::new();
932 let rows = rng.below(40);
933 for _ in 0..rows {
934 let fields = 1 + rng.below(5);
935 for field in 0..fields {
936 if field > 0 {
937 out.push(b",,,|\t"[rng.below(5)]);
938 }
939 let mut body = Vec::new();
940 for _ in 0..rng.below(6) {
941 let piece = PIECES[rng.below(PIECES.len())];
942 if rng.below(10) == 0 {
944 body.extend(std::iter::repeat_n(b'q', rng.below(90)));
945 }
946 body.extend_from_slice(piece);
947 }
948 match rng.below(4) {
949 0 => {
950 out.push(b'"');
951 for &byte in &body {
952 if byte == b'"' {
953 out.push(b'"');
954 }
955 out.push(byte);
956 }
957 out.push(b'"');
958 }
959 1 => {
960 out.push(b'"');
961 out.extend_from_slice(&body);
962 out.push(b'"');
963 }
964 _ => out.extend_from_slice(&body),
965 }
966 }
967 out.extend_from_slice([&b"\n"[..], b"\r\n", b"\r"][rng.below(3)]);
968 }
969 if rng.below(3) == 0 {
970 out.truncate(out.len().saturating_sub(1 + rng.below(3)));
971 }
972 out
973 }
974
975 #[test]
976 fn thousands_of_generated_files_split_the_same_both_ways() {
977 let mut rng = Rng(0x9e37_79b9_7f4a_7c15);
978 for _ in 0..3000 {
979 let bytes = generate(&mut rng);
980 let cuts: Vec<usize> = (0..4).map(|_| rng.below(bytes.len() + 1)).collect();
981 for dialect in dialects() {
982 check(&bytes, dialect, &cuts);
983 }
984 }
985 }
986}