1use std::borrow::Cow;
37
38use pdfrum_common::{Limits, hex_digit};
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum CharClass {
43 Whitespace,
45 Numeric,
47 Delimiter,
49 Regular,
51}
52
53#[must_use]
69pub const fn class_of(byte: u8) -> CharClass {
70 match byte {
71 0x00 | 0x09 | 0x0A | 0x0C | 0x0D | 0x20 | 0x80 | 0xFF => CharClass::Whitespace,
75 b'0'..=b'9' | b'+' | b'-' | b'.' => CharClass::Numeric,
76 b'%' | b'(' | b')' | b'/' | b'<' | b'>' | b'[' | b']' | b'{' | b'}' => CharClass::Delimiter,
77 _ => CharClass::Regular,
78 }
79}
80
81#[must_use]
83pub fn is_whitespace(byte: u8) -> bool {
84 class_of(byte) == CharClass::Whitespace
85}
86
87#[must_use]
89pub fn is_numeric(byte: u8) -> bool {
90 class_of(byte) == CharClass::Numeric
91}
92
93#[must_use]
95pub fn is_delimiter(byte: u8) -> bool {
96 class_of(byte) == CharClass::Delimiter
97}
98
99#[must_use]
102pub fn is_line_ending(byte: u8) -> bool {
103 byte == b'\r' || byte == b'\n'
104}
105
106#[derive(Debug, Clone, PartialEq, Eq)]
114pub enum Token<'a> {
115 Number(&'a [u8]),
119 Name(&'a [u8]),
122 Keyword(&'a [u8]),
125 Delim(Delim),
127 Eof,
129}
130
131impl<'a> Token<'a> {
132 #[must_use]
135 pub fn bytes(&self) -> &'a [u8] {
136 match self {
137 Self::Number(b) | Self::Name(b) | Self::Keyword(b) => b,
138 Self::Delim(d) => d.as_bytes(),
139 Self::Eof => b"",
140 }
141 }
142
143 #[must_use]
145 pub fn is_number(&self) -> bool {
146 matches!(self, Self::Number(_))
147 }
148
149 #[must_use]
151 pub fn is_eof(&self) -> bool {
152 matches!(self, Self::Eof)
153 }
154
155 #[must_use]
157 pub fn is_keyword(&self, word: &[u8]) -> bool {
158 matches!(self, Self::Keyword(b) if *b == word)
159 }
160}
161
162#[derive(Debug, Clone, Copy, PartialEq, Eq)]
164pub enum Delim {
165 ArrayOpen,
167 ArrayClose,
169 DictOpen,
171 DictClose,
173 HexOpen,
175 HexClose,
177 StringOpen,
179 StringClose,
181 BraceOpen,
183 BraceClose,
185 Percent,
188}
189
190impl Delim {
191 #[must_use]
193 pub fn as_bytes(self) -> &'static [u8] {
194 match self {
195 Self::ArrayOpen => b"[",
196 Self::ArrayClose => b"]",
197 Self::DictOpen => b"<<",
198 Self::DictClose => b">>",
199 Self::HexOpen => b"<",
200 Self::HexClose => b">",
201 Self::StringOpen => b"(",
202 Self::StringClose => b")",
203 Self::BraceOpen => b"{",
204 Self::BraceClose => b"}",
205 Self::Percent => b"%",
206 }
207 }
208}
209
210#[derive(Debug, Clone)]
230pub struct Lexer<'a> {
231 bytes: &'a [u8],
232 pos: usize,
233}
234
235impl<'a> Lexer<'a> {
236 #[must_use]
238 pub fn new(bytes: &'a [u8]) -> Self {
239 Self { bytes, pos: 0 }
240 }
241
242 #[must_use]
244 pub fn at(bytes: &'a [u8], pos: usize) -> Self {
245 Self {
246 bytes,
247 pos: pos.min(bytes.len()),
248 }
249 }
250
251 #[must_use]
253 pub fn bytes(&self) -> &'a [u8] {
254 self.bytes
255 }
256
257 #[must_use]
259 pub fn pos(&self) -> usize {
260 self.pos
261 }
262
263 pub fn seek(&mut self, pos: usize) {
265 self.pos = pos.min(self.bytes.len());
266 }
267
268 #[must_use]
270 pub fn at_eof(&self) -> bool {
271 self.pos >= self.bytes.len()
272 }
273
274 #[must_use]
276 pub fn peek_byte(&self) -> Option<u8> {
277 self.bytes.get(self.pos).copied()
278 }
279
280 fn read_byte(&mut self) -> Option<u8> {
282 let b = self.bytes.get(self.pos).copied()?;
283 self.pos += 1;
284 Some(b)
285 }
286
287 fn unread(&mut self) {
289 self.pos = self.pos.saturating_sub(1);
290 }
291
292 pub fn skip_to_word(&mut self) {
298 while let Some(b) = self.peek_byte() {
299 if is_whitespace(b) {
300 self.pos += 1;
301 } else if b == b'%' {
302 self.skip_comment();
303 } else {
304 return;
305 }
306 }
307 }
308
309 fn skip_comment(&mut self) {
311 while let Some(b) = self.peek_byte() {
312 if is_line_ending(b) {
313 return;
314 }
315 self.pos += 1;
316 }
317 }
318
319 pub fn to_next_line(&mut self) {
325 while let Some(b) = self.read_byte() {
326 if b == b'\n' {
327 return;
328 }
329 if b == b'\r' {
330 if self.peek_byte() == Some(b'\n') {
331 self.pos += 1;
332 }
333 return;
334 }
335 }
336 }
337
338 pub fn skip_eol_marker(&mut self) -> usize {
342 match self.peek_byte() {
343 Some(b'\r') => {
344 self.pos += 1;
345 if self.peek_byte() == Some(b'\n') {
346 self.pos += 1;
347 2
348 } else {
349 1
350 }
351 }
352 Some(b'\n') => {
353 self.pos += 1;
354 1
355 }
356 _ => 0,
357 }
358 }
359
360 pub fn next_word(&mut self, limits: &Limits) -> Token<'a> {
365 self.skip_to_word();
366 let Some(first) = self.read_byte() else {
367 return Token::Eof;
368 };
369
370 if is_delimiter(first) {
371 return self.delimiter_token(first, limits);
372 }
373
374 let start = self.pos - 1;
380 let mut all_numeric = is_numeric(first);
381 while let Some(b) = self.read_byte() {
382 if is_whitespace(b) || is_delimiter(b) {
383 self.unread();
384 break;
385 }
386 all_numeric &= is_numeric(b);
387 }
388 let word = truncate(self.bytes.get(start..self.pos).unwrap_or_default(), limits);
389 if all_numeric {
390 Token::Number(word)
391 } else {
392 Token::Keyword(word)
393 }
394 }
395
396 fn delimiter_token(&mut self, first: u8, limits: &Limits) -> Token<'a> {
398 match first {
399 b'/' => {
403 let start = self.pos;
404 while let Some(b) = self.peek_byte() {
405 if matches!(class_of(b), CharClass::Regular | CharClass::Numeric) {
406 self.pos += 1;
407 } else {
408 break;
409 }
410 }
411 let payload = self.bytes.get(start..self.pos).unwrap_or_default();
416 let budget = limits.max_word_len.saturating_sub(1);
417 Token::Name(payload.get(..budget).unwrap_or(payload))
418 }
419 b'<' => {
420 if self.peek_byte() == Some(b'<') {
421 self.pos += 1;
422 Token::Delim(Delim::DictOpen)
423 } else {
424 Token::Delim(Delim::HexOpen)
425 }
426 }
427 b'>' => {
428 if self.peek_byte() == Some(b'>') {
429 self.pos += 1;
430 Token::Delim(Delim::DictClose)
431 } else {
432 Token::Delim(Delim::HexClose)
433 }
434 }
435 b'[' => Token::Delim(Delim::ArrayOpen),
436 b']' => Token::Delim(Delim::ArrayClose),
437 b'(' => Token::Delim(Delim::StringOpen),
438 b')' => Token::Delim(Delim::StringClose),
439 b'{' => Token::Delim(Delim::BraceOpen),
440 b'}' => Token::Delim(Delim::BraceClose),
441 _ => Token::Delim(Delim::Percent),
442 }
443 }
444
445 pub fn peek_word(&mut self, limits: &Limits) -> Token<'a> {
448 let saved = self.pos;
449 let token = self.next_word(limits);
450 self.pos = saved;
451 token
452 }
453
454 pub fn read_literal_string(&mut self) -> Cow<'a, [u8]> {
464 let start = self.pos;
465 let mut out: Option<Vec<u8>> = None;
466 let mut depth: u32 = 0;
467 let mut verbatim_end = start;
470
471 while let Some(b) = self.read_byte() {
472 match b {
473 b'(' => {
474 depth += 1;
475 push(&mut out, verbatim_end, b);
476 verbatim_end = self.pos;
477 }
478 b')' => {
479 if depth == 0 {
480 return finish(self.bytes, start, verbatim_end, out);
481 }
482 depth -= 1;
483 push(&mut out, verbatim_end, b);
484 verbatim_end = self.pos;
485 }
486 b'\\' => {
487 let buf = out.get_or_insert_with(|| {
489 self.bytes
490 .get(start..verbatim_end)
491 .unwrap_or_default()
492 .to_vec()
493 });
494 self.read_escape(buf);
495 verbatim_end = self.pos;
496 }
497 _ => {
498 push(&mut out, verbatim_end, b);
499 verbatim_end = self.pos;
500 }
501 }
502 }
503 finish(self.bytes, start, verbatim_end, out)
504 }
505
506 fn read_escape(&mut self, out: &mut Vec<u8>) {
508 let Some(b) = self.read_byte() else { return };
509 match b {
510 b'n' => out.push(b'\n'),
511 b'r' => out.push(b'\r'),
512 b't' => out.push(b'\t'),
513 b'b' => out.push(0x08),
514 b'f' => out.push(0x0C),
515 b'\r' => {
518 if self.peek_byte() == Some(b'\n') {
519 self.pos += 1;
520 }
521 }
522 b'\n' => {}
523 b'0'..=b'7' => {
524 let mut value: u32 = u32::from(b - b'0');
527 for _ in 0..2 {
528 match self.peek_byte() {
529 Some(d @ b'0'..=b'7') => {
530 self.pos += 1;
531 value = value * 8 + u32::from(d - b'0');
532 }
533 _ => break,
534 }
535 }
536 out.push(u8::try_from(value & 0xFF).unwrap_or(0));
538 }
539 other => out.push(other),
542 }
543 }
544
545 pub fn read_hex_string(&mut self) -> Vec<u8> {
553 let mut out = Vec::new();
554 let mut high: Option<u8> = None;
555 while let Some(b) = self.read_byte() {
556 if b == b'>' {
557 break;
558 }
559 let Some(nibble) = hex_digit(b) else { continue };
560 match high.take() {
561 None => high = Some(nibble),
562 Some(h) => out.push((h << 4) | nibble),
563 }
564 }
565 if let Some(h) = high {
566 out.push(h << 4);
567 }
568 out
569 }
570
571 pub fn search_back(&mut self, word: &[u8], window: usize) -> bool {
585 if word.is_empty() || self.pos + 1 < word.len() {
586 return false;
587 }
588 let limit = self.pos.saturating_sub(window);
589 let mut candidate = (self.pos + 1).saturating_sub(word.len());
590 loop {
591 if self.bytes.get(candidate..candidate + word.len()) == Some(word)
592 && is_whole_word(
593 self.bytes,
594 candidate,
595 word.len(),
596 WordBoundary::WhitespaceOrDelimiter,
597 )
598 {
599 self.pos = candidate;
600 return true;
601 }
602 if candidate == 0 || candidate <= limit {
603 return false;
604 }
605 candidate -= 1;
606 }
607 }
608}
609
610fn push(out: &mut Option<Vec<u8>>, _verbatim_end: usize, b: u8) {
612 if let Some(buf) = out {
613 buf.push(b);
614 }
615}
616
617fn finish(bytes: &[u8], start: usize, verbatim_end: usize, out: Option<Vec<u8>>) -> Cow<'_, [u8]> {
619 match out {
620 Some(buf) => Cow::Owned(buf),
621 None => Cow::Borrowed(bytes.get(start..verbatim_end).unwrap_or_default()),
622 }
623}
624
625fn truncate<'a>(word: &'a [u8], limits: &Limits) -> &'a [u8] {
627 word.get(..limits.max_word_len).unwrap_or(word)
628}
629
630#[derive(Debug, Clone, Copy, PartialEq, Eq)]
636pub enum WordBoundary {
637 WhitespaceOnly,
640 WhitespaceOrDelimiter,
644}
645
646impl WordBoundary {
647 #[must_use]
649 fn accepts(self, b: u8) -> bool {
650 match self {
651 Self::WhitespaceOnly => is_whitespace(b),
652 Self::WhitespaceOrDelimiter => {
653 !matches!(class_of(b), CharClass::Regular | CharClass::Numeric)
654 }
655 }
656 }
657}
658
659#[must_use]
661pub fn is_whole_word(bytes: &[u8], pos: usize, len: usize, rule: WordBoundary) -> bool {
662 let boundary = |b: u8| rule.accepts(b);
663 if pos > 0 && !bytes.get(pos - 1).copied().is_some_and(boundary) {
664 return false;
665 }
666 match bytes.get(pos + len) {
667 None => true,
668 Some(&b) => boundary(b),
669 }
670}
671
672#[must_use]
675pub fn find_word(bytes: &[u8], word: &[u8], from: usize, rule: WordBoundary) -> Option<usize> {
676 if word.is_empty() || from > bytes.len() {
677 return None;
678 }
679 let last = bytes.len().checked_sub(word.len())?;
680 (from..=last).find(|&i| {
681 bytes.get(i..i + word.len()) == Some(word) && is_whole_word(bytes, i, word.len(), rule)
682 })
683}
684
685#[must_use]
693pub fn atoui(word: &[u8]) -> u32 {
694 let (negative, digits) = match word.split_first() {
695 Some((b'-', rest)) => (true, rest),
696 Some((b'+', rest)) => (false, rest),
697 _ => (false, word),
698 };
699 let mut value: u32 = 0;
700 for &b in digits {
701 let Some(d) = (b as char).to_digit(10) else {
702 break;
703 };
704 value = match value.checked_mul(10).and_then(|v| v.checked_add(d)) {
705 Some(v) => v,
706 None => return u32::MAX,
707 };
708 }
709 if negative {
710 (!value).wrapping_add(1)
711 } else {
712 value
713 }
714}
715
716#[must_use]
721pub fn atoi64(word: &[u8]) -> i64 {
722 let (negative, digits) = match word.split_first() {
723 Some((b'-', rest)) => (true, rest),
724 Some((b'+', rest)) => (false, rest),
725 _ => (false, word),
726 };
727 let mut value: i64 = 0;
728 for &b in digits {
729 let Some(d) = (b as char).to_digit(10) else {
730 break;
731 };
732 value = match value
733 .checked_mul(10)
734 .and_then(|v| v.checked_add(i64::from(d)))
735 {
736 Some(v) => v,
737 None => return if negative { i64::MIN } else { i64::MAX },
738 };
739 }
740 if negative { -value } else { value }
741}
742
743#[cfg(test)]
744mod tests {
745 use super::{
746 CharClass, Delim, Lexer, Token, WordBoundary, atoi64, atoui, class_of, find_word,
747 is_whole_word,
748 };
749 use pdfrum_common::Limits;
750
751 fn limits() -> Limits {
752 Limits::default()
753 }
754
755 #[test]
756 fn classifies_the_two_pdfium_quirks() {
757 assert_eq!(class_of(0x80), CharClass::Whitespace);
758 assert_eq!(class_of(0xFF), CharClass::Whitespace);
759 assert_eq!(class_of(0x0B), CharClass::Regular);
760 assert_eq!(class_of(b'.'), CharClass::Numeric);
761 assert_eq!(class_of(b'%'), CharClass::Delimiter);
762 }
763
764 #[test]
765 fn high_bytes_separate_words() {
766 let bytes = [b'a', 0x80, b'b', 0xFF, b'c'];
767 let mut lx = Lexer::new(&bytes);
768 assert_eq!(lx.next_word(&limits()), Token::Keyword(b"a"));
769 assert_eq!(lx.next_word(&limits()), Token::Keyword(b"b"));
770 assert_eq!(lx.next_word(&limits()), Token::Keyword(b"c"));
771 assert!(lx.next_word(&limits()).is_eof());
772 }
773
774 #[test]
775 fn vertical_tab_stays_inside_a_name() {
776 let bytes = [b'/', b'a', 0x0B, b'b', b' '];
777 let mut lx = Lexer::new(&bytes);
778 assert_eq!(lx.next_word(&limits()), Token::Name(&[b'a', 0x0B, b'b']));
779 }
780
781 #[test]
782 fn number_tokens_are_shape_not_value() {
783 let mut lx = Lexer::new(b"--37 1.2.3 +-. 12a");
784 assert_eq!(lx.next_word(&limits()), Token::Number(b"--37"));
785 assert_eq!(lx.next_word(&limits()), Token::Number(b"1.2.3"));
786 assert_eq!(lx.next_word(&limits()), Token::Number(b"+-."));
787 assert_eq!(lx.next_word(&limits()), Token::Keyword(b"12a"));
788 }
789
790 #[test]
791 fn delimiters_pair_and_push_back() {
792 let mut lx = Lexer::new(b"<</a[1]>>><");
793 assert_eq!(lx.next_word(&limits()), Token::Delim(Delim::DictOpen));
794 assert_eq!(lx.next_word(&limits()), Token::Name(b"a"));
795 assert_eq!(lx.next_word(&limits()), Token::Delim(Delim::ArrayOpen));
796 assert_eq!(lx.next_word(&limits()), Token::Number(b"1"));
797 assert_eq!(lx.next_word(&limits()), Token::Delim(Delim::ArrayClose));
798 assert_eq!(lx.next_word(&limits()), Token::Delim(Delim::DictClose));
799 assert_eq!(lx.next_word(&limits()), Token::Delim(Delim::HexClose));
800 assert_eq!(lx.next_word(&limits()), Token::Delim(Delim::HexOpen));
801 }
802
803 #[test]
804 fn a_bare_slash_is_the_empty_name() {
805 let mut lx = Lexer::new(b"/ /Name/Other");
806 assert_eq!(lx.next_word(&limits()), Token::Name(b""));
807 assert_eq!(lx.next_word(&limits()), Token::Name(b"Name"));
808 assert_eq!(lx.next_word(&limits()), Token::Name(b"Other"));
809 }
810
811 #[test]
812 fn comments_vanish() {
813 let mut lx = Lexer::new(b"% one\n%two\n 42");
814 assert_eq!(lx.next_word(&limits()), Token::Number(b"42"));
815 }
816
817 #[test]
818 fn two_long_names_collide_one_byte_sooner_than_keywords() {
819 let name = |tail: u8| {
821 let mut v = vec![b'/'];
822 v.extend(std::iter::repeat_n(b'a', 255));
823 v.push(tail);
824 v.push(b' ');
825 v
826 };
827 let (x, y) = (name(b'x'), name(b'y'));
828 assert_eq!(
829 Lexer::new(&x).next_word(&limits()),
830 Lexer::new(&y).next_word(&limits())
831 );
832 }
833
834 #[test]
835 fn words_truncate_at_the_limit() {
836 let long = vec![b'a'; 300];
837 let mut source = long.clone();
838 source.push(b' ');
839 source.push(b'z');
840 let mut lx = Lexer::new(&source);
841 let token = lx.next_word(&limits());
842 assert_eq!(token.bytes().len(), 256);
843 assert_eq!(lx.next_word(&limits()), Token::Keyword(b"z"));
845 }
846
847 #[test]
848 fn names_truncate_one_byte_sooner_than_keywords() {
849 let mut source = vec![b'/'];
852 source.extend(std::iter::repeat_n(b'x', 300));
853 let mut lx = Lexer::new(&source);
854 assert_eq!(lx.next_word(&limits()).bytes().len(), 255);
855 }
856
857 #[test]
858 fn peek_is_position_neutral() {
859 let mut lx = Lexer::new(b" hello world");
860 let before = lx.pos();
861 assert_eq!(lx.peek_word(&limits()), Token::Keyword(b"hello"));
862 assert_eq!(lx.pos(), before);
863 assert_eq!(lx.next_word(&limits()), Token::Keyword(b"hello"));
864 }
865
866 #[test]
867 fn literal_string_escapes() {
868 let cases: &[(&[u8], &[u8])] = &[
869 (b"abc)", b"abc"),
870 (b"a(b)c)", b"a(b)c"),
871 (b"\\n\\r\\t\\b\\f)", b"\n\r\t\x08\x0C"),
872 (b"\\101)", b"A"),
873 (b"\\777)", b"\xFF"),
874 (b"\\(\\)\\\\)", b"()\\"),
875 (b"a\\\nb)", b"ab"),
876 (b"a\\\r\nb)", b"ab"),
877 (b"a\\\rb)", b"ab"),
878 (b"\\q)", b"q"),
879 (b"abc", b"abc"),
881 ];
882 for (input, expected) in cases {
883 let mut lx = Lexer::new(input);
884 assert_eq!(&*lx.read_literal_string(), *expected, "input {input:?}");
885 }
886 }
887
888 #[test]
889 fn literal_string_borrows_when_it_can() {
890 let mut lx = Lexer::new(b"plain)");
891 assert!(matches!(
892 lx.read_literal_string(),
893 std::borrow::Cow::Borrowed(_)
894 ));
895 }
896
897 #[test]
898 fn hex_string_skips_everything_it_does_not_understand() {
899 let cases: &[(&[u8], &[u8], usize)] = &[
901 (b"1A2b>abcd", b"\x1a\x2b", 5),
902 (b"1A2>abcd", b"\x1a\x20", 4),
903 (b"z12b>abcd", b"\x12\xb0", 5),
904 (b"*<&*#$^&@1>abcd", b"\x10", 11),
905 (b"\x00z12b>", b"\x12\xb0", 6),
906 (b"12&%^*b>", b"\x12\xb0", 8),
907 (b"1A2b", b"\x1a\x2b", 4),
908 (b"1A2", b"\x1a\x20", 3),
909 (b"", b"", 0),
910 (b">", b"", 1),
911 ];
912 for (input, expected, end) in cases {
913 let mut lx = Lexer::new(input);
914 assert_eq!(&lx.read_hex_string(), expected, "input {input:?}");
915 assert_eq!(lx.pos(), *end, "end position for {input:?}");
916 }
917 }
918
919 #[test]
920 fn to_next_line_treats_crlf_as_one() {
921 let mut lx = Lexer::new(b"abc\r\ndef");
922 lx.to_next_line();
923 assert_eq!(lx.pos(), 5);
924 let mut lx = Lexer::new(b"abc\rdef");
925 lx.to_next_line();
926 assert_eq!(lx.pos(), 4);
927 let mut lx = Lexer::new(b"abc\ndef");
928 lx.to_next_line();
929 assert_eq!(lx.pos(), 4);
930 let mut lx = Lexer::new(b"abc");
932 lx.to_next_line();
933 assert_eq!(lx.pos(), 3);
934 }
935
936 #[test]
937 fn eol_markers_count_their_bytes() {
938 assert_eq!(Lexer::new(b"\r\nx").skip_eol_marker(), 2);
939 assert_eq!(Lexer::new(b"\rx").skip_eol_marker(), 1);
940 assert_eq!(Lexer::new(b"\nx").skip_eol_marker(), 1);
941 assert_eq!(Lexer::new(b"x").skip_eol_marker(), 0);
942 }
943
944 #[test]
945 fn whole_word_boundaries_differ_by_strictness() {
946 let bytes = b">>endstream ";
947 assert!(!is_whole_word(bytes, 2, 9, WordBoundary::WhitespaceOnly));
949 assert!(is_whole_word(
951 bytes,
952 2,
953 9,
954 WordBoundary::WhitespaceOrDelimiter
955 ));
956 }
957
958 #[test]
959 fn find_word_respects_the_keyword_rule() {
960 let bytes = b"x >>endstream y endstream z";
961 assert_eq!(
962 find_word(bytes, b"endstream", 0, WordBoundary::WhitespaceOnly),
963 Some(16)
964 );
965 assert_eq!(
966 find_word(bytes, b"endstream", 0, WordBoundary::WhitespaceOrDelimiter),
967 Some(4)
968 );
969 assert_eq!(
970 find_word(bytes, b"nothere", 0, WordBoundary::WhitespaceOnly),
971 None
972 );
973 }
974
975 #[test]
976 fn search_back_finds_the_last_occurrence() {
977 let bytes = b"startxref 1\nstartxref 2\n";
978 let mut lx = Lexer::at(bytes, bytes.len());
979 assert!(lx.search_back(b"startxref", 4096));
980 assert_eq!(lx.pos(), 12);
981 }
982
983 #[test]
984 fn search_back_includes_the_byte_under_the_cursor() {
985 let file = b"%PDF-1.7\nstartxref 1234567";
992 let start = 9;
993 let cursor = file.len() - 9;
994 assert_eq!(file.get(start..start + 9), Some(&b"startxref"[..]));
995 assert_eq!(start + 8, cursor);
997
998 let mut lx = Lexer::at(file, cursor);
999 assert!(lx.search_back(b"startxref", 4096));
1000 assert_eq!(lx.pos(), start);
1001 }
1002
1003 #[test]
1004 fn search_back_declines_a_word_that_does_not_fit() {
1005 let mut lx = Lexer::at(b"xref", 1);
1006 assert!(!lx.search_back(b"startxref", 4096));
1007 assert_eq!(lx.pos(), 1);
1008 let mut lx = Lexer::at(b"abc", 2);
1010 assert!(lx.search_back(b"abc", 4096));
1011 assert_eq!(lx.pos(), 0);
1012 }
1013
1014 #[test]
1015 fn atoui_saturates_and_negates() {
1016 assert_eq!(atoui(b"0"), 0);
1017 assert_eq!(atoui(b"42"), 42);
1018 assert_eq!(atoui(b"4294967295"), u32::MAX);
1019 assert_eq!(atoui(b"99999999999"), u32::MAX);
1020 assert_eq!(atoui(b"-1"), u32::MAX);
1021 assert_eq!(atoui(b"-2"), u32::MAX - 1);
1022 assert_eq!(atoui(b"12a34"), 12);
1023 assert_eq!(atoui(b""), 0);
1024 }
1025
1026 #[test]
1027 fn atoi64_saturates() {
1028 assert_eq!(atoi64(b"-5"), -5);
1029 assert_eq!(atoi64(b"100940"), 100_940);
1030 assert_eq!(atoi64(b"999999999999999999999"), i64::MAX);
1031 }
1032
1033 #[test]
1034 fn never_panics_on_arbitrary_bytes() {
1035 for seed in 0u8..=255 {
1036 let bytes: Vec<u8> = (0..64u8)
1037 .map(|i| i.wrapping_mul(7).wrapping_add(seed))
1038 .collect();
1039 let mut lx = Lexer::new(&bytes);
1040 for _ in 0..200 {
1041 if lx.next_word(&limits()).is_eof() {
1042 break;
1043 }
1044 }
1045 }
1046 }
1047}