1use crate::error::Result;
13use crate::object::Name;
14
15#[derive(Debug, Clone, PartialEq)]
17pub enum Token {
18 Int(i64),
19 Real(f64),
20 Name(Name),
21 LitString(Vec<u8>),
23 HexString(Vec<u8>),
25 ArrayOpen,
26 ArrayClose,
27 DictOpen,
28 DictClose,
29 Keyword(Vec<u8>),
33 Eof,
34}
35
36pub(crate) fn is_whitespace(b: u8) -> bool {
38 matches!(b, b'\0' | b'\t' | b'\n' | b'\x0C' | b'\r' | b' ')
39}
40
41pub(crate) fn is_delimiter(b: u8) -> bool {
43 matches!(
44 b,
45 b'(' | b')' | b'<' | b'>' | b'[' | b']' | b'{' | b'}' | b'/' | b'%'
46 )
47}
48
49pub(crate) fn is_regular(b: u8) -> bool {
51 !is_whitespace(b) && !is_delimiter(b)
52}
53
54fn hex_val(b: u8) -> Option<u8> {
56 match b {
57 b'0'..=b'9' => Some(b - b'0'),
58 b'a'..=b'f' => Some(b - b'a' + 10),
59 b'A'..=b'F' => Some(b - b'A' + 10),
60 _ => None,
61 }
62}
63
64pub struct Lexer<'a> {
66 data: &'a [u8],
67 pos: usize,
68}
69
70impl<'a> Lexer<'a> {
71 pub fn new(data: &'a [u8]) -> Self {
73 Lexer { data, pos: 0 }
74 }
75
76 pub fn at(data: &'a [u8], pos: usize) -> Self {
78 Lexer { data, pos }
79 }
80
81 pub fn pos(&self) -> usize {
83 self.pos
84 }
85
86 pub fn seek(&mut self, pos: usize) {
88 self.pos = pos;
89 }
90
91 pub fn next_token(&mut self) -> Result<Token> {
93 self.skip_whitespace_and_comments();
94 let Some(&b) = self.data.get(self.pos) else {
95 return Ok(Token::Eof);
96 };
97 match b {
98 b'[' => {
99 self.pos += 1;
100 Ok(Token::ArrayOpen)
101 }
102 b']' => {
103 self.pos += 1;
104 Ok(Token::ArrayClose)
105 }
106 b'<' => {
107 if self.data.get(self.pos + 1) == Some(&b'<') {
108 self.pos += 2;
109 Ok(Token::DictOpen)
110 } else {
111 self.pos += 1;
112 Ok(self.lex_hex_string())
113 }
114 }
115 b'>' => {
116 if self.data.get(self.pos + 1) == Some(&b'>') {
117 self.pos += 2;
118 Ok(Token::DictClose)
119 } else {
120 self.pos += 1;
122 Ok(Token::Keyword(vec![b'>']))
123 }
124 }
125 b'(' => {
126 self.pos += 1;
127 Ok(self.lex_literal_string())
128 }
129 b'/' => {
130 self.pos += 1;
131 Ok(self.lex_name())
132 }
133 b')' | b'{' | b'}' => {
135 self.pos += 1;
136 Ok(Token::Keyword(vec![b]))
137 }
138 b'0'..=b'9' | b'+' | b'-' | b'.' => Ok(self.lex_number_or_keyword()),
139 _ => Ok(self.lex_keyword()),
140 }
141 }
142
143 pub fn peek_token(&mut self) -> Result<Token> {
145 let save = self.pos;
146 let token = self.next_token();
147 self.pos = save;
148 token
149 }
150
151 pub fn skip_whitespace_and_comments(&mut self) {
153 loop {
154 while self.data.get(self.pos).is_some_and(|&b| is_whitespace(b)) {
155 self.pos += 1;
156 }
157 if self.data.get(self.pos) == Some(&b'%') {
158 while self
159 .data
160 .get(self.pos)
161 .is_some_and(|&b| b != b'\r' && b != b'\n')
162 {
163 self.pos += 1;
164 }
165 } else {
166 return;
167 }
168 }
169 }
170
171 pub fn data(&self) -> &'a [u8] {
173 self.data
174 }
175
176 fn take_regular_run(&mut self) -> &'a [u8] {
178 let start = self.pos;
179 while self.data.get(self.pos).is_some_and(|&b| is_regular(b)) {
180 self.pos += 1;
181 }
182 &self.data[start..self.pos]
183 }
184
185 fn lex_number_or_keyword(&mut self) -> Token {
188 let run = self.take_regular_run();
189 if !run
190 .iter()
191 .all(|&b| matches!(b, b'0'..=b'9' | b'+' | b'-' | b'.'))
192 {
193 return Token::Keyword(run.to_vec());
194 }
195 if let Ok(s) = std::str::from_utf8(run) {
201 if !run.contains(&b'.') {
202 if let Ok(value) = s.parse::<i64>() {
203 return Token::Int(value);
204 }
205 }
206 if let Ok(value) = s.parse::<f64>() {
207 return Token::Real(value);
208 }
209 }
210 let mut bytes = run.iter().copied();
213 let negative = match run.first() {
214 Some(b'-') => {
215 bytes.next();
216 true
217 }
218 Some(b'+') => {
219 bytes.next();
220 false
221 }
222 _ => false,
223 };
224 let mut digits = String::new();
225 let mut seen_dot = false;
226 for b in bytes {
227 match b {
228 b'0'..=b'9' => digits.push(char::from(b)),
229 b'.' if !seen_dot => {
230 seen_dot = true;
231 digits.push('.');
232 }
233 _ => {}
234 }
235 }
236 if seen_dot {
237 let value = if digits == "." {
238 0.0
239 } else {
240 digits.parse::<f64>().unwrap_or(0.0)
241 };
242 Token::Real(if negative { -value } else { value })
243 } else if digits.is_empty() {
244 Token::Int(0)
246 } else if let Ok(value) = digits.parse::<i64>() {
247 Token::Int(if negative { -value } else { value })
248 } else {
249 let value = digits.parse::<f64>().unwrap_or(0.0);
251 Token::Real(if negative { -value } else { value })
252 }
253 }
254
255 fn lex_keyword(&mut self) -> Token {
257 Token::Keyword(self.take_regular_run().to_vec())
258 }
259
260 fn lex_name(&mut self) -> Token {
263 let start = self.pos;
264 while let Some(&b) = self.data.get(self.pos) {
265 if !is_regular(b) {
266 break;
267 }
268 self.pos += 1;
269 }
270 let run = &self.data[start..self.pos];
271 if !run.contains(&b'#') {
274 return Token::Name(Name(String::from_utf8_lossy(run).into_owned()));
275 }
276 let mut out = Vec::with_capacity(run.len());
280 let mut i = 0;
281 while i < run.len() {
282 let b = run[i];
283 if b == b'#' && i + 2 < run.len() {
284 if let (Some(hi), Some(lo)) = (hex_val(run[i + 1]), hex_val(run[i + 2])) {
285 out.push((hi << 4) | lo);
286 i += 3;
287 continue;
288 }
289 }
290 out.push(b);
291 i += 1;
292 }
293 Token::Name(Name(String::from_utf8_lossy(&out).into_owned()))
294 }
295
296 fn lex_literal_string(&mut self) -> Token {
301 let mut out = Vec::new();
302 let mut depth = 1usize;
303 while let Some(&b) = self.data.get(self.pos) {
304 self.pos += 1;
305 match b {
306 b'\\' => {
307 let Some(&esc) = self.data.get(self.pos) else {
308 break; };
310 self.pos += 1;
311 match esc {
312 b'n' => out.push(b'\n'),
313 b'r' => out.push(b'\r'),
314 b't' => out.push(b'\t'),
315 b'b' => out.push(0x08),
316 b'f' => out.push(0x0C),
317 b'(' => out.push(b'('),
318 b')' => out.push(b')'),
319 b'\\' => out.push(b'\\'),
320 b'0'..=b'7' => {
321 let mut value = u32::from(esc - b'0');
322 for _ in 0..2 {
323 match self.data.get(self.pos) {
324 Some(&d @ b'0'..=b'7') => {
325 value = value * 8 + u32::from(d - b'0');
326 self.pos += 1;
327 }
328 _ => break,
329 }
330 }
331 out.push((value & 0xFF) as u8);
332 }
333 b'\r' => {
334 if self.data.get(self.pos) == Some(&b'\n') {
336 self.pos += 1;
337 }
338 }
339 b'\n' => {} other => out.push(other), }
342 }
343 b'(' => {
344 depth += 1;
345 out.push(b'(');
346 }
347 b')' => {
348 depth -= 1;
349 if depth == 0 {
350 return Token::LitString(out);
351 }
352 out.push(b')');
353 }
354 b'\r' => {
355 if self.data.get(self.pos) == Some(&b'\n') {
357 self.pos += 1;
358 }
359 out.push(b'\n');
360 }
361 other => out.push(other),
362 }
363 }
364 Token::LitString(out)
365 }
366
367 fn lex_hex_string(&mut self) -> Token {
371 let mut out = Vec::new();
372 let mut pending: Option<u8> = None;
373 while let Some(&b) = self.data.get(self.pos) {
374 self.pos += 1;
375 if b == b'>' {
376 break;
377 }
378 let Some(v) = hex_val(b) else {
379 continue; };
381 match pending.take() {
382 Some(hi) => out.push((hi << 4) | v),
383 None => pending = Some(v),
384 }
385 }
386 if let Some(hi) = pending {
387 out.push(hi << 4);
388 }
389 Token::HexString(out)
390 }
391}
392
393#[cfg(test)]
394mod tests {
395 use super::*;
396
397 fn toks(src: &[u8]) -> Vec<Token> {
399 let mut lexer = Lexer::new(src);
400 let mut out = Vec::new();
401 loop {
402 let token = lexer.next_token().expect("lexing must not fail");
403 if token == Token::Eof {
404 return out;
405 }
406 out.push(token);
407 }
408 }
409
410 fn one(src: &[u8]) -> Token {
411 let mut all = toks(src);
412 assert_eq!(all.len(), 1, "expected exactly one token in {src:?}");
413 all.pop().unwrap()
414 }
415
416 #[test]
417 fn numeric_forms() {
418 assert_eq!(
419 toks(b"+17 -98 34.5 -3.62 .5 4. -.002"),
420 vec![
421 Token::Int(17),
422 Token::Int(-98),
423 Token::Real(34.5),
424 Token::Real(-3.62),
425 Token::Real(0.5),
426 Token::Real(4.0),
427 Token::Real(-0.002),
428 ]
429 );
430 assert_eq!(one(b"0"), Token::Int(0));
431 assert_eq!(one(b"123"), Token::Int(123));
432 assert_eq!(one(b"0.0"), Token::Real(0.0));
433 }
434
435 #[test]
436 fn lenient_numbers() {
437 assert_eq!(one(b"--5"), Token::Int(-5));
439 assert_eq!(one(b"+-3"), Token::Int(3));
440 assert_eq!(one(b"1.2.3"), Token::Real(1.23));
442 assert_eq!(one(b"."), Token::Real(0.0));
444 assert_eq!(one(b"-"), Token::Int(0));
445 assert_eq!(one(b"99999999999999999999"), Token::Real(1e20));
447 assert_eq!(one(b"1e5"), Token::Keyword(b"1e5".to_vec()));
449 }
450
451 #[test]
452 fn structural_delimiters() {
453 assert_eq!(
454 toks(b"[]<<>>"),
455 vec![
456 Token::ArrayOpen,
457 Token::ArrayClose,
458 Token::DictOpen,
459 Token::DictClose,
460 ]
461 );
462 assert_eq!(
463 toks(b"<< /Type /Page >>"),
464 vec![
465 Token::DictOpen,
466 Token::Name(Name("Type".into())),
467 Token::Name(Name("Page".into())),
468 Token::DictClose,
469 ]
470 );
471 }
472
473 #[test]
474 fn stray_delimiters_are_lenient_keywords() {
475 assert_eq!(one(b")"), Token::Keyword(b")".to_vec()));
476 assert_eq!(one(b"{"), Token::Keyword(b"{".to_vec()));
477 assert_eq!(one(b"}"), Token::Keyword(b"}".to_vec()));
478 }
479
480 #[test]
481 fn names() {
482 assert_eq!(one(b"/Name1"), Token::Name(Name("Name1".into())));
483 assert_eq!(one(b"/A#42"), Token::Name(Name("AB".into())));
484 assert_eq!(one(b"/Bad#zz"), Token::Name(Name("Bad#zz".into())));
485 assert_eq!(
486 one(b"/Lime#20Green"),
487 Token::Name(Name("Lime Green".into()))
488 );
489 assert_eq!(
490 one(b"/paired#28#29parentheses"),
491 Token::Name(Name("paired()parentheses".into()))
492 );
493 assert_eq!(one(b"/A#6f"), Token::Name(Name("Ao".into())));
495 assert_eq!(one(b"/A#4"), Token::Name(Name("A#4".into())));
497 assert_eq!(one(b"/"), Token::Name(Name(String::new())));
499 assert_eq!(
501 toks(b"/A#4/B"),
502 vec![
503 Token::Name(Name("A#4".into())),
504 Token::Name(Name("B".into())),
505 ]
506 );
507 }
508
509 #[test]
510 fn literal_string_basics() {
511 assert_eq!(one(b"()"), Token::LitString(Vec::new()));
512 assert_eq!(one(b"(hello)"), Token::LitString(b"hello".to_vec()));
513 assert_eq!(one(b"(a(b)c)"), Token::LitString(b"a(b)c".to_vec()));
514 assert_eq!(
515 one(b"(deep(er(and(deeper))))"),
516 Token::LitString(b"deep(er(and(deeper)))".to_vec())
517 );
518 }
519
520 #[test]
521 fn literal_string_every_escape_form() {
522 assert_eq!(
523 one(b"(\\n\\r\\t\\b\\f\\(\\)\\\\)"),
524 Token::LitString(vec![b'\n', b'\r', b'\t', 0x08, 0x0C, b'(', b')', b'\\'])
525 );
526 assert_eq!(one(b"(\\q)"), Token::LitString(b"q".to_vec()));
528 }
529
530 #[test]
531 fn literal_string_octal_escapes() {
532 assert_eq!(one(b"(\\053)"), Token::LitString(b"+".to_vec()));
533 assert_eq!(one(b"(\\53)"), Token::LitString(b"+".to_vec()));
534 assert_eq!(one(b"(\\5)"), Token::LitString(vec![0x05]));
535 assert_eq!(one(b"(\\0053)"), Token::LitString(vec![0x05, b'3']));
537 assert_eq!(one(b"(\\400)"), Token::LitString(vec![0x00]));
539 assert_eq!(one(b"(\\777)"), Token::LitString(vec![0xFF]));
540 assert_eq!(one(b"(\\1x)"), Token::LitString(vec![0x01, b'x']));
542 }
543
544 #[test]
545 fn literal_string_line_continuations() {
546 assert_eq!(one(b"(ab\\\ncd)"), Token::LitString(b"abcd".to_vec()));
547 assert_eq!(one(b"(ab\\\rcd)"), Token::LitString(b"abcd".to_vec()));
548 assert_eq!(one(b"(ab\\\r\ncd)"), Token::LitString(b"abcd".to_vec()));
549 }
550
551 #[test]
552 fn literal_string_eol_normalization() {
553 assert_eq!(one(b"(a\nb)"), Token::LitString(b"a\nb".to_vec()));
554 assert_eq!(one(b"(a\rb)"), Token::LitString(b"a\nb".to_vec()));
555 assert_eq!(one(b"(a\r\nb)"), Token::LitString(b"a\nb".to_vec()));
556 }
557
558 #[test]
559 fn literal_string_unterminated_is_lenient() {
560 assert_eq!(one(b"(abc"), Token::LitString(b"abc".to_vec()));
561 assert_eq!(one(b"(abc\\"), Token::LitString(b"abc".to_vec()));
562 }
563
564 #[test]
565 fn hex_strings() {
566 assert_eq!(one(b"<>"), Token::HexString(Vec::new()));
567 assert_eq!(one(b"<901FA3>"), Token::HexString(vec![0x90, 0x1F, 0xA3]));
568 assert_eq!(one(b"<901FA>"), Token::HexString(vec![0x90, 0x1F, 0xA0]));
570 assert_eq!(
572 one(b"<48 65\n6C\t6C 6F>"),
573 Token::HexString(b"Hello".to_vec())
574 );
575 assert_eq!(
577 one(b"<deadBEEF>"),
578 Token::HexString(vec![0xDE, 0xAD, 0xBE, 0xEF])
579 );
580 assert_eq!(one(b"<41"), Token::HexString(vec![0x41]));
582 }
583
584 #[test]
585 fn comments() {
586 assert_eq!(
587 toks(b"1 % comment ( with ) delimiters <</junk>>\n2"),
588 vec![Token::Int(1), Token::Int(2)]
589 );
590 assert_eq!(toks(b"%PDF-1.7\n42"), vec![Token::Int(42)]);
591 assert_eq!(toks(b"% c\r7"), vec![Token::Int(7)]);
593 assert_eq!(toks(b"5 % trailing"), vec![Token::Int(5)]);
595 assert_eq!(toks(b"%%EOF"), Vec::new());
596 }
597
598 #[test]
599 fn keywords() {
600 let src = b"obj endobj stream endstream R xref trailer startxref true false null n f";
601 let expected: Vec<Token> = src
602 .split(|&b| b == b' ')
603 .map(|w| Token::Keyword(w.to_vec()))
604 .collect();
605 assert_eq!(toks(src), expected);
606 }
607
608 #[test]
609 fn indirect_reference_shape() {
610 assert_eq!(
611 toks(b"12 0 R"),
612 vec![Token::Int(12), Token::Int(0), Token::Keyword(b"R".to_vec()),]
613 );
614 }
615
616 #[test]
617 fn eof_behavior() {
618 let mut lexer = Lexer::new(b"");
619 assert_eq!(lexer.next_token().unwrap(), Token::Eof);
620 assert_eq!(lexer.next_token().unwrap(), Token::Eof);
622
623 let mut lexer = Lexer::new(b" \t\r\n \x00\x0C ");
624 assert_eq!(lexer.next_token().unwrap(), Token::Eof);
625
626 let mut lexer = Lexer::new(b"1");
627 assert_eq!(lexer.next_token().unwrap(), Token::Int(1));
628 assert_eq!(lexer.next_token().unwrap(), Token::Eof);
629 assert_eq!(lexer.next_token().unwrap(), Token::Eof);
630
631 let mut lexer = Lexer::new(b"abc");
633 lexer.seek(100);
634 assert_eq!(lexer.next_token().unwrap(), Token::Eof);
635 }
636
637 #[test]
638 fn peek_does_not_consume() {
639 let mut lexer = Lexer::new(b"/A 1");
640 let before = lexer.pos();
641 assert_eq!(lexer.peek_token().unwrap(), Token::Name(Name("A".into())));
642 assert_eq!(lexer.pos(), before, "peek must not move the cursor");
643 assert_eq!(lexer.peek_token().unwrap(), Token::Name(Name("A".into())));
644 assert_eq!(lexer.next_token().unwrap(), Token::Name(Name("A".into())));
645 assert_eq!(lexer.peek_token().unwrap(), Token::Int(1));
646 assert_eq!(lexer.next_token().unwrap(), Token::Int(1));
647 assert_eq!(lexer.peek_token().unwrap(), Token::Eof);
648 }
649
650 #[test]
651 fn seek_round_trips() {
652 let src = b"[ /Key (val) 42 ]";
653 let mut lexer = Lexer::new(src);
654 assert_eq!(lexer.next_token().unwrap(), Token::ArrayOpen);
655 let mark = lexer.pos();
656 assert_eq!(lexer.next_token().unwrap(), Token::Name(Name("Key".into())));
657 assert_eq!(
658 lexer.next_token().unwrap(),
659 Token::LitString(b"val".to_vec())
660 );
661 lexer.seek(mark);
663 assert_eq!(lexer.next_token().unwrap(), Token::Name(Name("Key".into())));
664 assert_eq!(
665 lexer.next_token().unwrap(),
666 Token::LitString(b"val".to_vec())
667 );
668 assert_eq!(lexer.next_token().unwrap(), Token::Int(42));
669 assert_eq!(lexer.next_token().unwrap(), Token::ArrayClose);
670
671 let mut resumed = Lexer::at(src, mark);
673 assert_eq!(
674 resumed.next_token().unwrap(),
675 Token::Name(Name("Key".into()))
676 );
677 assert_eq!(resumed.data(), src);
678 }
679
680 #[test]
681 fn skip_whitespace_and_comments_stops_at_token() {
682 let mut lexer = Lexer::new(b" % one\n % two\r\n 7");
683 lexer.skip_whitespace_and_comments();
684 assert_eq!(lexer.data()[lexer.pos()], b'7');
685 let pos = lexer.pos();
687 lexer.skip_whitespace_and_comments();
688 assert_eq!(lexer.pos(), pos);
689 }
690
691 #[test]
692 fn mixed_stream_of_tokens() {
693 assert_eq!(
694 toks(b"<</N 3/Root 1 0 R>>[(a)<62>/c true]"),
695 vec![
696 Token::DictOpen,
697 Token::Name(Name("N".into())),
698 Token::Int(3),
699 Token::Name(Name("Root".into())),
700 Token::Int(1),
701 Token::Int(0),
702 Token::Keyword(b"R".to_vec()),
703 Token::DictClose,
704 Token::ArrayOpen,
705 Token::LitString(b"a".to_vec()),
706 Token::HexString(b"b".to_vec()),
707 Token::Name(Name("c".into())),
708 Token::Keyword(b"true".to_vec()),
709 Token::ArrayClose,
710 ]
711 );
712 }
713
714 #[test]
715 fn character_classes() {
716 for b in [0x00u8, b'\t', b'\n', 0x0C, b'\r', b' '] {
717 assert!(is_whitespace(b), "{b:#04x} should be whitespace");
718 assert!(!is_regular(b));
719 }
720 for b in *b"()<>[]{}/%" {
721 assert!(is_delimiter(b), "{} should be a delimiter", b as char);
722 assert!(!is_regular(b));
723 }
724 for b in *b"aZ09+-.#_*'\"" {
725 assert!(is_regular(b), "{} should be regular", b as char);
726 }
727 }
728}