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, lines) = 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 if lines >> bit & 1 == 0 {
426 field = at + 1;
427 continue;
428 }
429 let mut next = at + 1;
430 if bytes[at] == b'\r' {
431 match bytes.get(next) {
432 Some(b'\n') => next += 1,
433 Some(_) => {}
434 None if eof => {}
435 None => {
438 out.spans.truncate(mark);
439 return Ok(record);
440 }
441 }
442 }
443 out.ends.push(out.spans.len());
444 mark = out.spans.len();
445 record = next;
446 field = next;
447 if out.len() == limit {
448 return Ok(record);
449 }
450 }
451 if field < end && quotes >> field.saturating_sub(block) != 0 {
452 quoted = true;
453 }
454 block = end;
455 }
456 if out.len() >= limit || record >= len {
457 return Ok(record);
458 }
459 out.spans.truncate(mark);
463 let Some(next) = spans(bytes, record, dialect, eof, &mut out.spans)? else {
464 return Ok(record);
465 };
466 out.ends.push(out.spans.len());
467 record = next;
468 }
469}
470
471#[inline]
477fn masks(block: &[u8], delimiter: u8, quote: u8) -> (u64, u64, u64) {
478 let needles = [quote, delimiter, b'\n', b'\r'];
479 let [quotes, delimiters, newlines, returns] = if let Ok(full) = block.try_into() {
480 rudb_vector::bytes::masks(full, needles)
481 } else {
482 let mut padded = [0u8; 64];
483 padded[..block.len()].copy_from_slice(block);
484 let live = below(block.len());
485 rudb_vector::bytes::masks(&padded, needles).map(|mask| mask & live)
486 };
487 let lines = newlines | returns;
488 (quotes, delimiters | lines, lines)
489}
490
491const fn prefix_xor(mut bits: u64) -> u64 {
493 bits ^= bits << 1;
494 bits ^= bits << 2;
495 bits ^= bits << 4;
496 bits ^= bits << 8;
497 bits ^= bits << 16;
498 bits ^= bits << 32;
499 bits
500}
501
502const fn below(count: usize) -> u64 {
504 (1u64 << count) - 1
505}
506
507fn enclosed(field: &[u8], quote: u8) -> Option<bool> {
514 let [first, inner @ .., last] = field else { return None };
515 if *first != quote || *last != quote {
516 return None;
517 }
518 let mut escaped = false;
519 let mut at = 0;
520 while let Some(found) = inner[at..].iter().position(|&byte| byte == quote) {
521 if inner.get(at + found + 1) != Some("e) {
522 return None;
523 }
524 escaped = true;
525 at += found + 2;
526 }
527 Some(escaped)
528}
529
530fn spans(
535 bytes: &[u8],
536 from: usize,
537 dialect: Dialect,
538 eof: bool,
539 out: &mut Vec<Span>,
540) -> Result<Option<usize>> {
541 if from >= bytes.len() {
542 return Ok(None);
543 }
544 let mark = out.len();
545 let quote = dialect.quote_byte();
546 let escape = dialect.escape_byte();
547 let mut at = from;
548 loop {
549 let span;
550 if bytes.get(at) == Some("e) {
551 at += 1;
552 let start = at;
553 let mut escaped = false;
554 loop {
555 let Some(&byte) = bytes.get(at) else {
556 out.truncate(mark);
557 return Ok(None);
558 };
559 if byte == escape && bytes.get(at + 1) == Some("e) {
560 escaped = true;
561 at += 2;
562 continue;
563 }
564 if byte == quote {
565 break;
566 }
567 at += 1;
568 }
569 span = Span::new(start, at, escaped);
570 at += 1;
571 match bytes.get(at) {
572 None if !eof => {
573 out.truncate(mark);
574 return Ok(None);
575 }
576 None => {}
577 Some(&byte) if byte == dialect.delimiter || byte == b'\n' || byte == b'\r' => {}
578 Some(&byte) => {
579 out.truncate(mark);
580 return Err(after_quote(byte));
581 }
582 }
583 } else {
584 let start = at;
585 while let Some(&byte) = bytes.get(at) {
586 if byte == dialect.delimiter || byte == b'\n' || byte == b'\r' {
587 break;
588 }
589 at += 1;
590 }
591 if at >= bytes.len() && !eof {
592 out.truncate(mark);
593 return Ok(None);
594 }
595 span = Span::new(start, at, false);
596 }
597 out.push(span);
598 match bytes.get(at) {
599 Some(&byte) if byte == dialect.delimiter => at += 1,
600 Some(b'\r') => {
601 at += 1;
602 if bytes.get(at) == Some(&b'\n') {
603 at += 1;
604 } else if at >= bytes.len() && !eof {
605 out.truncate(mark);
606 return Ok(None);
607 }
608 break;
609 }
610 Some(b'\n') => {
611 at += 1;
612 break;
613 }
614 Some(_) => unreachable!("a field stops at a delimiter, a line ending or the end"),
615 None => break,
616 }
617 }
618 Ok(Some(at))
619}
620
621#[cfg(test)]
622mod tests {
623 use super::*;
624
625 fn split(bytes: &[u8], dialect: Dialect) -> Vec<Vec<String>> {
626 let mut rows = Vec::new();
627 let mut fields = Vec::new();
628 let mut at = 0;
629 while at < bytes.len() {
630 let next = record(bytes, at, dialect, true, &mut fields)
631 .expect("splits")
632 .expect("a whole record");
633 rows.push(fields.clone());
634 at = next;
635 }
636 rows
637 }
638
639 fn comma() -> Dialect {
640 Dialect { delimiter: b',', quote: Some(b'"'), escape: Some(b'"'), header: true }
641 }
642
643 #[test]
644 fn a_line_of_fields_is_the_fields_of_that_line() {
645 assert_eq!(split(b"a,b,c\n1,2,3\n", comma()), [["a", "b", "c"], ["1", "2", "3"]]);
646 }
647
648 #[test]
649 fn the_last_line_does_not_need_a_newline_on_it() {
650 assert_eq!(split(b"a,b\n1,2", comma()), [["a", "b"], ["1", "2"]]);
651 }
652
653 #[test]
654 fn all_three_line_endings_end_a_line() {
655 assert_eq!(split(b"a\r\nb\rc\n", comma()), [["a"], ["b"], ["c"]]);
656 }
657
658 #[test]
659 fn a_quoted_field_may_hold_the_delimiter_and_a_newline() {
660 assert_eq!(split(b"1,\"x,y\"\n", comma()), [["1", "x,y"]]);
661 assert_eq!(split(b"1,\"x\ny\"\n", comma()), [["1", "x\ny"]]);
662 }
663
664 #[test]
665 fn a_doubled_quote_inside_a_quoted_field_is_one_quote() {
666 assert_eq!(split(b"1,\"say \"\"hi\"\"\"\n", comma()), [["1", "say \"hi\""]]);
667 }
668
669 #[test]
670 fn an_empty_field_is_an_empty_string_here_and_becomes_a_null_above() {
671 assert_eq!(split(b"1,,3\n", comma()), [["1", "", "3"]]);
672 assert_eq!(split(b"1,\"\",3\n", comma()), [["1", "", "3"]]);
673 }
674
675 #[test]
676 fn a_trailing_delimiter_makes_a_last_empty_field() {
677 assert_eq!(split(b"1|x|\n", Dialect { delimiter: b'|', ..comma() }), [["1", "x", ""]]);
678 }
679
680 #[test]
681 fn a_quote_in_the_middle_of_a_bare_field_is_just_a_character() {
682 assert_eq!(split(b"1,he said \"hi\"\n", comma()), [["1", "he said \"hi\""]]);
683 }
684
685 #[test]
686 fn a_record_that_the_buffer_does_not_hold_all_of_is_not_a_record_yet() {
687 let mut fields = Vec::new();
688 assert_eq!(record(b"a,b", 0, comma(), false, &mut fields).unwrap(), None);
689 assert_eq!(record(b"a,\"b", 0, comma(), true, &mut fields).unwrap(), None);
690 assert_eq!(record(b"a,b\n", 0, comma(), false, &mut fields).unwrap(), Some(4));
691 }
692
693 #[test]
694 fn rubbish_after_a_closing_quote_is_an_error_rather_than_a_guess() {
695 let mut fields = Vec::new();
696 let error = record(b"\"x\"y,2\n", 0, comma(), true, &mut fields).unwrap_err();
697 assert!(error.message().contains("quoted value"), "{error}");
698 }
699
700 #[test]
701 fn utf8_survives_being_read_one_byte_at_a_time() {
702 assert_eq!(split("a,héllo\n".as_bytes(), comma()), [["a", "héllo"]]);
703 }
704}
705
706#[cfg(test)]
712mod agree {
713 use super::*;
714
715 type Outcome = (Vec<Vec<String>>, usize, Option<String>);
717
718 fn by_record(bytes: &[u8], from: usize, dialect: Dialect, eof: bool) -> Outcome {
719 let mut rows = Vec::new();
720 let mut fields = Vec::new();
721 let mut at = from;
722 loop {
723 match record(bytes, at, dialect, eof, &mut fields) {
724 Ok(Some(next)) => {
725 rows.push(fields.clone());
726 at = next;
727 }
728 Ok(None) => return (rows, at, None),
729 Err(error) => return (rows, at, Some(error.to_string())),
730 }
731 }
732 }
733
734 fn by_chunk(bytes: &[u8], from: usize, dialect: Dialect, eof: bool, limit: usize) -> Outcome {
735 let mut rows = Vec::new();
736 let mut at = from;
737 let mut out = Records::default();
738 loop {
739 out.clear();
740 let result = records(bytes, at, dialect, eof, limit, &mut out);
741 for row in 0..out.len() {
742 rows.push(
743 out.fields(row)
744 .iter()
745 .map(|span| span.text(bytes, dialect).into_owned())
746 .collect(),
747 );
748 }
749 match result {
750 Ok(next) => {
751 at = next;
752 if out.len() < limit {
753 return (rows, at, None);
754 }
755 }
756 Err(error) => {
757 let mut from = at;
760 let mut fields = Vec::new();
761 for _ in 0..out.len() {
762 from = record(bytes, from, dialect, eof, &mut fields).unwrap().unwrap();
763 }
764 return (rows, from, Some(error.to_string()));
765 }
766 }
767 }
768 }
769
770 fn check(bytes: &[u8], dialect: Dialect, cuts: &[usize]) {
773 for eof in [true, false] {
774 let expected = by_record(bytes, 0, dialect, eof);
775 for limit in [1, 2, 3, 7, 8192] {
776 assert_eq!(
777 by_chunk(bytes, 0, dialect, eof, limit),
778 expected,
779 "{:?} under {dialect:?}, eof {eof}, {limit} at a time",
780 String::from_utf8_lossy(bytes),
781 );
782 }
783 }
784 let whole = by_record(bytes, 0, dialect, true);
785 for &cut in cuts {
786 let cut = cut.min(bytes.len());
787 let (mut rows, at, error) = by_chunk(&bytes[..cut], 0, dialect, false, 8192);
790 if error.is_some() {
791 assert_eq!(error, whole.2, "an error in the front is the error in the whole");
792 continue;
793 }
794 let (rest, end, error) = by_chunk(bytes, at, dialect, true, 8192);
795 rows.extend(rest);
796 assert_eq!(
797 (rows, end, error),
798 whole,
799 "{:?} under {dialect:?} cut at {cut}",
800 String::from_utf8_lossy(bytes),
801 );
802 }
803 }
804
805 fn dialects() -> [Dialect; 5] {
806 let comma =
807 Dialect { delimiter: b',', quote: Some(b'"'), escape: Some(b'"'), header: false };
808 [
809 comma,
810 Dialect { quote: None, escape: None, ..comma },
811 Dialect { delimiter: b'|', ..comma },
812 Dialect { delimiter: b'\t', quote: Some(b'\''), escape: Some(b'\''), ..comma },
813 Dialect { escape: Some(b'\\'), ..comma },
814 ]
815 }
816
817 #[test]
818 fn the_tricky_ones_split_the_same_both_ways() {
819 let long = "x".repeat(61);
820 let cases: Vec<Vec<u8>> = vec![
821 b"".to_vec(),
822 b"\n".to_vec(),
823 b"\n\n".to_vec(),
824 b"a".to_vec(),
825 b"a,b,c\n1,2,3\n".to_vec(),
826 b"a,b\n1,2".to_vec(),
827 b"a\r\nb\rc\n".to_vec(),
828 b"a\r".to_vec(),
829 b"a\r\r\n\n".to_vec(),
830 b"1,\"x,y\"\n2,\"x\ny\"\n".to_vec(),
831 b"1,\"say \"\"hi\"\"\"\n".to_vec(),
832 b"1,,3\n1,\"\",3\n".to_vec(),
833 b"1|x|\n1,x,\n".to_vec(),
834 b"1,he said \"hi\"\n2,x\n".to_vec(),
835 b"1,he said \"hi\n2,x\n".to_vec(),
836 b"\"x\"y,2\n".to_vec(),
837 b"\"x\"\"\n".to_vec(),
838 b"\"x\"\"".to_vec(),
839 b"a,\"b".to_vec(),
840 b"a,\"b\"".to_vec(),
841 b"\"".to_vec(),
842 b"\"\"\"\"\n".to_vec(),
843 b"\"a\"\"\",b\n".to_vec(),
844 b"\"a\\\"b\",c\n\"a\\\\\"\n".to_vec(),
845 b"'a,b'\t'c''d'\n".to_vec(),
846 b"x\xffy,\"\xfe\"\"\"\n".to_vec(),
847 "h\u{e9}llo,w\u{f6}rld\n".as_bytes().to_vec(),
848 format!("{long},\"a\nb\",c\n{long}\r\n\"{long}\"\"{long}\",d\n").into_bytes(),
849 format!("{long}ab\r\n{long}abc\r\n").into_bytes(),
850 format!("\"{long}\"\"\",\"\n\"\n{long},x\"\n").into_bytes(),
851 ];
852 for bytes in &cases {
853 let cuts: Vec<usize> = (0..=bytes.len()).collect();
854 for dialect in dialects() {
855 check(bytes, dialect, &cuts);
856 }
857 }
858 }
859
860 struct Rng(u64);
862
863 impl Rng {
864 fn next(&mut self) -> u64 {
865 self.0 ^= self.0 << 13;
866 self.0 ^= self.0 >> 7;
867 self.0 ^= self.0 << 17;
868 self.0
869 }
870
871 fn below(&mut self, n: usize) -> usize {
872 (self.next() % n as u64) as usize
873 }
874 }
875
876 fn generate(rng: &mut Rng) -> Vec<u8> {
878 const PIECES: [&[u8]; 20] = [
879 b"a",
880 b"1",
881 b"-2.5",
882 b"xyz",
883 b",",
884 b"|",
885 b"\t",
886 b"\"",
887 b"\"\"",
888 b"'",
889 b"\\",
890 b"\n",
891 b"\r",
892 b"\r\n",
893 b" ",
894 b"\xc3\xa9",
895 b"\xff",
896 b"2020-01-02",
897 b"",
898 b"0123456789abcdef",
899 ];
900 let mut out = Vec::new();
901 let rows = rng.below(40);
902 for _ in 0..rows {
903 let fields = 1 + rng.below(5);
904 for field in 0..fields {
905 if field > 0 {
906 out.push(b",,,|\t"[rng.below(5)]);
907 }
908 let mut body = Vec::new();
909 for _ in 0..rng.below(6) {
910 let piece = PIECES[rng.below(PIECES.len())];
911 if rng.below(10) == 0 {
913 body.extend(std::iter::repeat_n(b'q', rng.below(90)));
914 }
915 body.extend_from_slice(piece);
916 }
917 match rng.below(4) {
918 0 => {
919 out.push(b'"');
920 for &byte in &body {
921 if byte == b'"' {
922 out.push(b'"');
923 }
924 out.push(byte);
925 }
926 out.push(b'"');
927 }
928 1 => {
929 out.push(b'"');
930 out.extend_from_slice(&body);
931 out.push(b'"');
932 }
933 _ => out.extend_from_slice(&body),
934 }
935 }
936 out.extend_from_slice([&b"\n"[..], b"\r\n", b"\r"][rng.below(3)]);
937 }
938 if rng.below(3) == 0 {
939 out.truncate(out.len().saturating_sub(1 + rng.below(3)));
940 }
941 out
942 }
943
944 #[test]
945 fn thousands_of_generated_files_split_the_same_both_ways() {
946 let mut rng = Rng(0x9e37_79b9_7f4a_7c15);
947 for _ in 0..3000 {
948 let bytes = generate(&mut rng);
949 let cuts: Vec<usize> = (0..4).map(|_| rng.below(bytes.len() + 1)).collect();
950 for dialect in dialects() {
951 check(&bytes, dialect, &cuts);
952 }
953 }
954 }
955}