1use proc_macro2::{Delimiter, Ident, Spacing, Span, TokenTree};
28use syn::Lit;
29
30pub trait HasSpan {
43 fn span(&self) -> Span;
56}
57
58struct GroupLevel {
60 iter: TokenStreamIter,
61 delimiter: Delimiter,
62 span: Span,
63}
64
65pub type TokenStreamIter = proc_macro2::token_stream::IntoIter;
79
80#[derive(Clone, Debug)]
95pub enum PunctOp {
96 One(char),
98 Two([char; 2]),
100 Three([char; 3]),
102}
103
104impl PartialEq<str> for PunctOp {
105 fn eq(&self, other: &str) -> bool {
107 let chars: &[char] = match self {
108 PunctOp::One(c) => std::slice::from_ref(c),
109 PunctOp::Two(chars) => chars,
110 PunctOp::Three(chars) => chars,
111 };
112 chars.iter().copied().eq(other.chars())
113 }
114}
115
116impl PartialEq<&str> for PunctOp {
117 fn eq(&self, other: &&str) -> bool {
119 self.eq(*other)
120 }
121}
122
123pub struct LexLexer {
150 input: TokenStreamIter,
151 group_stack: Vec<GroupLevel>,
154 pending_close: Option<(Delimiter, Span)>,
156 pending_token: Option<TokenTree>,
158}
159
160impl LexLexer {
161 pub fn new(input: TokenStreamIter) -> Self {
172 Self {
173 input,
174 group_stack: Vec::new(),
175 pending_close: None,
176 pending_token: None,
177 }
178 }
179
180 fn convert_literal(literal: proc_macro2::Literal) -> Token {
186 let span = literal.span();
187 match syn::parse2(TokenTree::Literal(literal).into()) {
188 Ok(literal) => Token::Literal(literal),
189 Err(error) => Token::Error {
190 message: error.to_string(),
191 span,
192 },
193 }
194 }
195
196 fn convert_identifier(ident: Ident) -> Token {
200 if ident == "true" || ident == "false" {
201 Token::Literal(Lit::Bool(syn::LitBool::new(ident == "true", ident.span())))
202 } else {
203 Token::Identifier(ident)
204 }
205 }
206
207 fn is_compound_operator(first: char, second: char) -> bool {
209 matches!(
210 (first, second),
211 ('&', '&')
212 | ('|', '|')
213 | ('=', '=')
214 | ('!', '=')
215 | ('<', '=')
216 | ('>', '=')
217 | ('<', '<')
218 | ('>', '>')
219 | ('-', '>')
220 | ('=', '>')
221 | (':', '=')
222 | ('.', '.')
223 )
224 }
225
226 fn next_token_tree(&mut self) -> Option<TokenTree> {
231 if let Some(token) = self.pending_token.take() {
232 return Some(token);
233 }
234
235 if let Some(level) = self.group_stack.last_mut() {
236 if let Some(tt) = level.iter.next() {
237 return Some(tt);
238 }
239 let level = self.group_stack.pop().unwrap();
240 self.pending_close = Some((level.delimiter, level.span));
241 return None;
242 }
243
244 self.input.next()
245 }
246
247 fn combine_punctuation(&mut self, first: proc_macro2::Punct) -> Token {
252 let ch = first.as_char();
253 let mut op = PunctOp::One(ch);
254 if first.spacing() == Spacing::Joint {
255 match self.next_token_tree() {
256 Some(TokenTree::Punct(second))
257 if Self::is_compound_operator(ch, second.as_char()) =>
258 {
259 let next_ch = second.as_char();
260 op = PunctOp::Two([ch, next_ch]);
261 if [ch, next_ch] == ['.', '.'] && second.spacing() == Spacing::Joint {
262 match self.next_token_tree() {
263 Some(TokenTree::Punct(third)) if third.as_char() == '=' => {
264 op = PunctOp::Three(['.', '.', '=']);
265 }
266 other => self.pending_token = other,
267 }
268 }
269 }
270 other => self.pending_token = other,
271 }
272 }
273 Token::Punct {
274 op,
275 span: first.span(),
276 }
277 }
278
279 fn parse_doc_comment_attribute(&mut self, hash_span: Span) -> Token {
286 let next = self.next_token_tree();
287 let (inner, group_token) = match next {
288 Some(TokenTree::Punct(p)) if p.as_char() == '!' => (true, self.next_token_tree()),
289 other => (false, other),
290 };
291 let group = match group_token {
292 Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Bracket => g,
293 Some(other) => {
294 return Token::Error {
295 message: "expected `[` after `#`".to_string(),
296 span: other.span(),
297 };
298 }
299 None => {
300 return Token::Error {
301 message: "expected `[` after `#`".to_string(),
302 span: hash_span,
303 };
304 }
305 };
306 let mut inner_tokens = group.stream().into_iter();
307 let ident_ok = match inner_tokens.next() {
308 Some(TokenTree::Ident(id)) => id == "doc",
309 _ => false,
310 };
311 if !ident_ok {
312 return Token::Error {
313 message: "expected `doc` after `#[`".to_string(),
314 span: group.span(),
315 };
316 }
317 let eq_ok = matches!(
318 inner_tokens.next(),
319 Some(TokenTree::Punct(p)) if p.as_char() == '='
320 );
321 if !eq_ok {
322 return Token::Error {
323 message: "expected `=` after `doc`".to_string(),
324 span: group.span(),
325 };
326 }
327 let text = match inner_tokens.next() {
328 Some(TokenTree::Literal(lit)) => {
329 let token_stream: proc_macro2::TokenStream = TokenTree::Literal(lit).into();
330 match syn::parse2::<Lit>(token_stream) {
331 Ok(Lit::Str(s)) => s.value(),
332 _ => {
333 return Token::Error {
334 message: "expected a string literal after `doc =`".to_string(),
335 span: group.span(),
336 };
337 }
338 }
339 }
340 _ => {
341 return Token::Error {
342 message: "expected a string literal after `doc =`".to_string(),
343 span: group.span(),
344 };
345 }
346 };
347 if inner_tokens.next().is_some() {
348 return Token::Error {
349 message: "unexpected token after `doc = \"...\"`".to_string(),
350 span: group.span(),
351 };
352 }
353 Token::DocComment {
354 text,
355 inner,
356 span: hash_span,
357 }
358 }
359}
360
361pub type Literal = Lit;
374
375impl HasSpan for Literal {
376 fn span(&self) -> Span {
378 syn::spanned::Spanned::span(self)
379 }
380}
381
382#[derive(Debug)]
398pub enum Token {
399 Literal(Literal),
401
402 Identifier(Ident),
404
405 Punct {
407 op: PunctOp,
409 span: Span,
411 },
412
413 OpenDelim {
415 delimiter: Delimiter,
417 span: Span,
419 },
420
421 CloseDelim {
423 delimiter: Delimiter,
425 span: Span,
427 },
428
429 DocComment {
433 text: String,
436 inner: bool,
438 span: Span,
440 },
441
442 Error {
444 message: String,
446 span: Span,
448 },
449}
450
451impl HasSpan for Token {
452 fn span(&self) -> Span {
454 match self {
455 Token::Literal(lit) => lit.span(),
456 Token::Identifier(ident) => ident.span(),
457 Token::Punct { span, .. } => *span,
458 Token::OpenDelim { span, .. } => *span,
459 Token::CloseDelim { span, .. } => *span,
460 Token::DocComment { span, .. } => *span,
461 Token::Error { span, .. } => *span,
462 }
463 }
464}
465
466impl HasSpan for TokenTree {
467 fn span(&self) -> Span {
469 TokenTree::span(self)
470 }
471}
472
473impl Iterator for LexLexer {
474 type Item = Token;
475
476 fn next(&mut self) -> Option<Self::Item> {
481 if let Some((delimiter, span)) = self.pending_close.take() {
482 return Some(Token::CloseDelim { delimiter, span });
483 }
484
485 let token = match self.next_token_tree() {
486 Some(token) => token,
487 None => {
488 return self
489 .pending_close
490 .take()
491 .map(|(delimiter, span)| Token::CloseDelim { delimiter, span });
492 }
493 };
494
495 Some(match token {
496 TokenTree::Group(group) => {
497 let delimiter = group.delimiter();
498 let span = group.span();
499 self.group_stack.push(GroupLevel {
500 iter: group.stream().into_iter(),
501 delimiter,
502 span,
503 });
504 Token::OpenDelim { delimiter, span }
505 }
506 TokenTree::Punct(punct) if punct.as_char() == '#' => {
507 self.parse_doc_comment_attribute(punct.span())
508 }
509 TokenTree::Punct(punct) => self.combine_punctuation(punct),
510 TokenTree::Literal(literal) => Self::convert_literal(literal),
511 TokenTree::Ident(ident) => Self::convert_identifier(ident),
512 })
513 }
514}
515
516#[cfg(test)]
517mod tests {
518 use super::*;
519 use proc_macro2::TokenStream;
520 use std::str::FromStr;
521
522 #[test]
524 fn punctuation_equality_compares_characters_without_prefix_matches() {
525 let cases = [
526 (PunctOp::One('+'), "+", true),
527 (PunctOp::One('+'), "++", false),
528 (PunctOp::One('+'), "", false),
529 (PunctOp::One('\u{a7}'), "\u{a7}", true),
530 (PunctOp::Two(['&', '&']), "&&", true),
531 (PunctOp::Two(['&', '&']), "&", false),
532 (PunctOp::Two(['&', '&']), "&&&", false),
533 (PunctOp::Two(['&', '&']), "&|", false),
534 (PunctOp::Three(['.', '.', '=']), "..=", true),
535 (PunctOp::Three(['.', '.', '=']), "..", false),
536 (PunctOp::Three(['.', '.', '=']), "..==", false),
537 (PunctOp::Three(['.', '.', '=']), "...", false),
538 ];
539 for (op, text, expected) in cases {
541 assert_eq!(op == *text, expected, "{op:?} versus {text:?}");
542 assert_eq!(op == text, expected, "{op:?} versus {text:?}");
543 }
544 }
545
546 #[test]
548 fn verbatim_literal_retains_its_span() {
549 let literal = proc_macro2::Literal::from_str("42").unwrap();
550 let literal = Lit::Verbatim(literal);
551 assert_eq!(HasSpan::span(&literal).source_text().as_deref(), Some("42"));
552 }
553
554 #[test]
556 fn punctuation_clones_are_independent_values() {
557 let cases = [
558 (PunctOp::One('+'), "+"),
559 (PunctOp::Two(['&', '&']), "&&"),
560 (PunctOp::Three(['.', '.', '=']), "..="),
561 ];
562 for (mut original, text) in cases {
564 let mut copied = original.clone();
565 assert!(copied == text);
566 match &mut original {
567 PunctOp::One(c) => *c = '!',
568 PunctOp::Two(chars) => chars[0] = '!',
569 PunctOp::Three(chars) => chars[0] = '!',
570 }
571 assert!(copied == text);
572 copied = PunctOp::One('?');
573 assert!(copied == "?");
574 assert!(original != "?");
575 assert!(original != text);
576 }
577 }
578
579 #[test]
581 fn literal_spans_preserve_the_source() {
582 for source in [
584 "42",
585 "1.5",
586 "\"text\"",
587 "b\"bytes\"",
588 "c\"cstr\"",
589 "'x'",
590 "b'x'",
591 "true",
592 "false",
593 ] {
594 let mut lexer = LexLexer::new(TokenStream::from_str(source).unwrap().into_iter());
595 let Some(Token::Literal(literal)) = lexer.next() else {
596 panic!("expected literal for {source}");
597 };
598 assert_eq!(
599 HasSpan::span(&literal).source_text().as_deref(),
600 Some(source)
601 );
602 assert_eq!(
603 HasSpan::span(&Token::Literal(literal))
604 .source_text()
605 .as_deref(),
606 Some(source)
607 );
608 assert!(lexer.next().is_none());
609 }
610 }
611
612 #[test]
614 fn token_tree_spans_cover_each_variant() {
615 for source in ["(x)", "name", "+", "42"] {
617 let tree = TokenStream::from_str(source)
618 .unwrap()
619 .into_iter()
620 .next()
621 .unwrap();
622 assert_eq!(HasSpan::span(&tree).source_text().as_deref(), Some(source));
623 }
624 }
625
626 #[test]
628 fn flattened_tokens_preserve_their_associated_spans() {
629 let cases: &[(&str, &[&str])] = &[
630 ("(x)", &["(x)", "x", "(x)"]),
631 ("&&", &["&"]),
632 ("#[bad]", &["[bad]"]),
633 ];
634 for &(source, spans) in cases {
636 let mut lexer = LexLexer::new(TokenStream::from_str(source).unwrap().into_iter());
637 for &expected in spans {
638 let token = lexer.next().unwrap();
639 assert_eq!(
640 HasSpan::span(&token).source_text().as_deref(),
641 Some(expected)
642 );
643 }
644 assert!(lexer.next().is_none());
645 }
646 }
647
648 #[test]
650 fn empty_input_remains_exhausted() {
651 let mut lexer = LexLexer::new(TokenStream::new().into_iter());
652 assert!(lexer.next().is_none());
653 assert!(lexer.next().is_none());
654 }
655
656 #[test]
658 fn combines_each_supported_operator() {
659 for text in [
661 "&&", "||", "==", "!=", "<=", ">=", "<<", ">>", "->", "=>", ":=", "..", "..=",
662 ] {
663 let input = TokenStream::from_str(&format!("{text} tail")).unwrap();
664 let mut lexer = LexLexer::new(input.into_iter());
665 assert!(matches!(lexer.next(), Some(Token::Punct { op, .. }) if op == text));
666 assert!(matches!(lexer.next(), Some(Token::Identifier(id)) if id == "tail"));
667 assert!(lexer.next().is_none());
668 }
669 }
670
671 #[test]
673 fn punctuation_lookahead_preserves_the_remaining_stream() {
674 let cases: &[(&str, &[&str])] = &[
675 ("& &", &["&", "&"]),
676 (".. =", &["..", "="]),
677 ("..+", &["..", "+"]),
678 ("&&=", &["&&", "="]),
679 ("+=", &["+", "="]),
680 ("...", &["..", "."]),
681 ];
682 for &(input, expected) in cases {
684 let mut lexer = LexLexer::new(TokenStream::from_str(input).unwrap().into_iter());
685 for &text in expected {
686 assert!(matches!(lexer.next(), Some(Token::Punct { op, .. }) if op == text));
687 }
688 assert!(lexer.next().is_none(), "{input}");
689 }
690 }
691
692 #[test]
694 fn empty_groups_preserve_delimiter_order() {
695 let mut lexer = LexLexer::new(TokenStream::from_str("([]{}) tail").unwrap().into_iter());
696 let expected = [
697 (true, Delimiter::Parenthesis),
698 (true, Delimiter::Bracket),
699 (false, Delimiter::Bracket),
700 (true, Delimiter::Brace),
701 (false, Delimiter::Brace),
702 (false, Delimiter::Parenthesis),
703 ];
704 for (open, expected_delimiter) in expected {
706 match lexer.next() {
707 Some(Token::OpenDelim { delimiter, .. }) if open => {
708 assert_eq!(delimiter, expected_delimiter);
709 }
710 Some(Token::CloseDelim { delimiter, .. }) if !open => {
711 assert_eq!(delimiter, expected_delimiter);
712 }
713 other => panic!("expected delimiter, got {other:?}"),
714 }
715 }
716 assert!(matches!(lexer.next(), Some(Token::Identifier(id)) if id == "tail"));
717 assert!(lexer.next().is_none());
718 }
719
720 #[test]
722 fn lookahead_does_not_consume_a_group_close() {
723 for punctuation in ["&", "..", "#", "#!"] {
725 let group = proc_macro2::Group::new(
726 Delimiter::Parenthesis,
727 punctuation
728 .chars()
729 .map(|ch| TokenTree::Punct(proc_macro2::Punct::new(ch, Spacing::Joint)))
730 .collect(),
731 );
732 let input: TokenStream = [
733 TokenTree::Group(group),
734 TokenTree::Ident(Ident::new("tail", Span::call_site())),
735 ]
736 .into_iter()
737 .collect();
738 let mut lexer = LexLexer::new(input.into_iter());
739 assert!(matches!(lexer.next(), Some(Token::OpenDelim { .. })));
740 match lexer.next() {
741 Some(Token::Punct { op, .. }) if !punctuation.starts_with('#') => {
742 assert!(op == punctuation);
743 }
744 Some(Token::Error { message, .. }) if punctuation.starts_with('#') => {
745 assert!(!message.is_empty());
746 }
747 other => panic!("expected punctuation or attribute error, got {other:?}"),
748 }
749 assert!(matches!(lexer.next(), Some(Token::CloseDelim { .. })));
750 assert!(matches!(lexer.next(), Some(Token::Identifier(id)) if id == "tail"));
751 assert!(lexer.next().is_none());
752 }
753 }
754
755 #[test]
757 fn invisible_groups_bound_operator_combining() {
758 let contents = [
759 TokenTree::Punct(proc_macro2::Punct::new('.', Spacing::Joint)),
760 TokenTree::Punct(proc_macro2::Punct::new('.', Spacing::Joint)),
761 ]
762 .into_iter()
763 .collect();
764 let input: TokenStream = [
765 TokenTree::Group(proc_macro2::Group::new(Delimiter::None, contents)),
766 TokenTree::Punct(proc_macro2::Punct::new('=', Spacing::Alone)),
767 ]
768 .into_iter()
769 .collect();
770 let mut lexer = LexLexer::new(input.into_iter());
771 assert!(matches!(
772 lexer.next(),
773 Some(Token::OpenDelim {
774 delimiter: Delimiter::None,
775 ..
776 })
777 ));
778 assert!(matches!(lexer.next(), Some(Token::Punct { op, .. }) if op == ".."));
779 assert!(matches!(
780 lexer.next(),
781 Some(Token::CloseDelim {
782 delimiter: Delimiter::None,
783 ..
784 })
785 ));
786 assert!(matches!(lexer.next(), Some(Token::Punct { op, .. }) if op == "="));
787 assert!(lexer.next().is_none());
788 }
789
790 #[test]
792 fn incomplete_attributes_at_end_of_input_report_errors() {
793 for source in ["#", "#!"] {
795 let mut lexer = LexLexer::new(TokenStream::from_str(source).unwrap().into_iter());
796 assert!(
797 matches!(lexer.next(), Some(Token::Error { message, .. }) if !message.is_empty())
798 );
799 assert!(lexer.next().is_none());
800 assert!(lexer.next().is_none());
801 }
802 }
803
804 #[test]
806 fn explicit_documentation_attributes_decode_string_values() {
807 let input =
808 TokenStream::from_str(r##"#[doc = "line\nnext"] #![doc = r#" raw "#]"##).unwrap();
809 let mut lexer = LexLexer::new(input.into_iter());
810 assert!(
811 matches!(lexer.next(), Some(Token::DocComment { text, inner: false, .. })
812 if text == "line\nnext")
813 );
814 assert!(
815 matches!(lexer.next(), Some(Token::DocComment { text, inner: true, .. })
816 if text == " raw ")
817 );
818 assert!(lexer.next().is_none());
819 }
820
821 #[test]
823 fn malformed_attributes_report_errors_and_resume() {
824 for input in [
826 "#[] tail",
827 "#[doc] tail",
828 "#[doc \"x\"] tail",
829 "#[doc =] tail",
830 "#[doc = value] tail",
831 "#[doc = b\"x\"] tail",
832 "#![doc = 1] tail",
833 "#[doc = \"x\" extra] tail",
834 ] {
835 let mut lexer = LexLexer::new(TokenStream::from_str(input).unwrap().into_iter());
836 assert!(
837 matches!(lexer.next(), Some(Token::Error { message, .. }) if !message.is_empty()),
838 "{input}"
839 );
840 assert!(matches!(lexer.next(), Some(Token::Identifier(id)) if id == "tail"));
841 assert!(lexer.next().is_none());
842 }
843 }
844
845 #[test]
847 fn boolean_recognition_preserves_other_identifiers() {
848 let input = TokenStream::from_str("true false r#true true_value").unwrap();
849 let mut lexer = LexLexer::new(input.into_iter());
850 assert!(matches!(lexer.next(), Some(Token::Literal(Lit::Bool(lit))) if lit.value));
851 assert!(matches!(lexer.next(), Some(Token::Literal(Lit::Bool(lit))) if !lit.value));
852 assert!(matches!(lexer.next(), Some(Token::Identifier(id)) if id == "r#true"));
853 assert!(matches!(lexer.next(), Some(Token::Identifier(id)) if id == "true_value"));
854 assert!(lexer.next().is_none());
855 }
856
857 #[test]
859 fn test_literal_integer() {
860 let input = TokenStream::from_str("42").unwrap();
861 let mut lexer = LexLexer::new(input.into_iter());
862
863 let token = lexer.next().unwrap();
864 match token {
865 Token::Literal(Lit::Int(..)) => {}
866 _ => panic!("Expected integer literal, got {:?}", token),
867 }
868 }
869
870 #[test]
872 fn test_literal_string() {
873 let input = TokenStream::from_str(r#""hello""#).unwrap();
874 let mut lexer = LexLexer::new(input.into_iter());
875
876 let token = lexer.next().unwrap();
877 match token {
878 Token::Literal(Lit::Str(..)) => {}
879 _ => panic!("Expected string literal"),
880 }
881 }
882
883 #[test]
885 fn test_literal_boolean() {
886 let input = TokenStream::from_str("true").unwrap();
888 let mut lexer = LexLexer::new(input.into_iter());
889
890 let token = lexer.next().unwrap();
891 match token {
892 Token::Literal(Lit::Bool(lit_bool)) => {
893 assert!(lit_bool.value);
894 }
895 _ => panic!("Expected boolean literal for 'true', got {:?}", token),
896 }
897
898 let input = TokenStream::from_str("false").unwrap();
900 let mut lexer = LexLexer::new(input.into_iter());
901
902 let token = lexer.next().unwrap();
903 match token {
904 Token::Literal(Lit::Bool(lit_bool)) => {
905 assert!(!lit_bool.value);
906 }
907 _ => panic!("Expected boolean literal for 'false', got {:?}", token),
908 }
909 }
910
911 #[test]
913 fn test_literal_float() {
914 let input = TokenStream::from_str("3.14").unwrap();
915 let mut lexer = LexLexer::new(input.into_iter());
916
917 let token = lexer.next().unwrap();
918 match token {
919 Token::Literal(Lit::Float(..)) => {}
920 _ => panic!("Expected float literal"),
921 }
922 }
923
924 #[test]
926 fn test_identifier() {
927 let input = TokenStream::from_str("foo").unwrap();
928 let mut lexer = LexLexer::new(input.into_iter());
929
930 let token = lexer.next().unwrap();
931 match token {
932 Token::Identifier(ident) => {
933 assert_eq!(ident.to_string(), "foo");
934 }
935 _ => panic!("Expected identifier"),
936 }
937 }
938
939 #[test]
941 fn test_punct() {
942 let input = TokenStream::from_str("+").unwrap();
943 let mut lexer = LexLexer::new(input.into_iter());
944
945 let token = lexer.next().unwrap();
946 match token {
947 Token::Punct { op, .. } => {
948 assert!(op == "+");
949 }
950 _ => panic!("Expected punctuation"),
951 }
952 }
953
954 #[test]
956 fn test_compound_operator() {
957 let input = TokenStream::from_str("a && b").unwrap();
958 let lexer = LexLexer::new(input.into_iter());
959
960 let tokens: Vec<_> = lexer.collect();
961 assert_eq!(tokens.len(), 3);
962
963 match &tokens[1] {
964 Token::Punct { op, .. } => {
965 assert!(op == "&&");
966 }
967 _ => panic!("Expected && operator"),
968 }
969 }
970
971 #[test]
973 fn test_group_flattening() {
974 let input = TokenStream::from_str("(10 + 20)").unwrap();
975 let lexer = LexLexer::new(input.into_iter());
976
977 let tokens: Vec<_> = lexer.collect();
979 assert_eq!(tokens.len(), 5);
980
981 assert!(matches!(
982 tokens[0],
983 Token::OpenDelim {
984 delimiter: Delimiter::Parenthesis,
985 ..
986 }
987 ));
988 assert!(matches!(tokens[1], Token::Literal(Lit::Int(..))));
989 assert!(matches!(&tokens[2], Token::Punct { op, .. } if op == "+"));
990 assert!(matches!(tokens[3], Token::Literal(Lit::Int(..))));
991 assert!(matches!(
992 tokens[4],
993 Token::CloseDelim {
994 delimiter: Delimiter::Parenthesis,
995 ..
996 }
997 ));
998 }
999
1000 #[test]
1002 fn test_nested_groups() {
1003 let input = TokenStream::from_str("(10 + (20 * 30))").unwrap();
1004 let lexer = LexLexer::new(input.into_iter());
1005
1006 let tokens: Vec<_> = lexer.collect();
1007
1008 assert_eq!(tokens.len(), 9);
1010
1011 assert!(matches!(tokens[0], Token::OpenDelim { .. }));
1013 assert!(matches!(tokens[1], Token::Literal(Lit::Int(..))));
1014 assert!(matches!(&tokens[2], Token::Punct { op, .. } if op == "+"));
1015 assert!(matches!(tokens[3], Token::OpenDelim { .. }));
1016 assert!(matches!(tokens[4], Token::Literal(Lit::Int(..))));
1017 assert!(matches!(&tokens[5], Token::Punct { op, .. } if op == "*"));
1018 assert!(matches!(tokens[6], Token::Literal(Lit::Int(..))));
1019 assert!(matches!(tokens[7], Token::CloseDelim { .. }));
1020 assert!(matches!(tokens[8], Token::CloseDelim { .. }));
1021 }
1022
1023 #[test]
1025 fn test_span_preservation() {
1026 let input = TokenStream::from_str("foo").unwrap();
1027 let mut lexer = LexLexer::new(input.into_iter());
1028
1029 let token = lexer.next().unwrap();
1030
1031 let span = HasSpan::span(&token);
1033 assert!(!span.source_text().unwrap_or_default().is_empty());
1034 }
1035
1036 #[test]
1038 fn test_mixed_tokens() {
1039 let input = TokenStream::from_str("foo + 42").unwrap();
1040 let lexer = LexLexer::new(input.into_iter());
1041
1042 let tokens: Vec<_> = lexer.collect();
1043 assert_eq!(tokens.len(), 3);
1044
1045 assert!(matches!(tokens[0], Token::Identifier(_)));
1046 assert!(matches!(&tokens[1], Token::Punct { op, .. } if op == "+"));
1047 assert!(matches!(tokens[2], Token::Literal(Lit::Int(..))));
1048 }
1049
1050 #[test]
1052 fn arrow_is_two_char_punct() {
1053 let stream: TokenStream = "->".parse().unwrap();
1054 let mut lexer = LexLexer::new(stream.into_iter());
1055 let tok = lexer.next().expect("one token");
1056 assert!(
1057 matches!(
1058 tok,
1059 Token::Punct {
1060 op: PunctOp::Two(['-', '>']),
1061 ..
1062 }
1063 ),
1064 "expected PunctOp::Two([\"->\"])), got {tok:?}"
1065 );
1066 assert!(lexer.next().is_none(), "expected no more tokens");
1067 }
1068
1069 #[test]
1071 fn fat_arrow_is_two_char_punct() {
1072 let stream: TokenStream = "=>".parse().unwrap();
1073 let mut lexer = LexLexer::new(stream.into_iter());
1074 let tok = lexer.next().expect("one token");
1075 assert!(
1076 matches!(
1077 tok,
1078 Token::Punct {
1079 op: PunctOp::Two(['=', '>']),
1080 ..
1081 }
1082 ),
1083 "expected PunctOp::Two([\"=>\"]), got {tok:?}"
1084 );
1085 assert!(lexer.next().is_none(), "expected no more tokens");
1086 }
1087
1088 #[test]
1090 fn recognizes_an_outer_doc_comment() {
1091 let input = TokenStream::from_str("/// the total").unwrap();
1092 let mut lexer = LexLexer::new(input.into_iter());
1093 let token = lexer.next().unwrap();
1094 match token {
1095 Token::DocComment { text, inner, .. } => {
1096 assert_eq!(text, " the total");
1097 assert!(!inner);
1098 }
1099 other => panic!("expected DocComment, got {other:?}"),
1100 }
1101 assert!(lexer.next().is_none());
1102 }
1103
1104 #[test]
1106 fn recognizes_an_inner_doc_comment() {
1107 let input = TokenStream::from_str("//! module docs").unwrap();
1108 let mut lexer = LexLexer::new(input.into_iter());
1109 let token = lexer.next().unwrap();
1110 match token {
1111 Token::DocComment { text, inner, .. } => {
1112 assert_eq!(text, " module docs");
1113 assert!(inner);
1114 }
1115 other => panic!("expected DocComment, got {other:?}"),
1116 }
1117 }
1118
1119 #[test]
1121 fn doc_comment_run_is_followed_by_the_next_real_token() {
1122 let input = TokenStream::from_str("/// x\ncell").unwrap();
1123 let mut lexer = LexLexer::new(input.into_iter());
1124 assert!(matches!(lexer.next(), Some(Token::DocComment { .. })));
1125 match lexer.next() {
1126 Some(Token::Identifier(ident)) => assert_eq!(ident.to_string(), "cell"),
1127 other => panic!("expected Identifier(cell), got {other:?}"),
1128 }
1129 }
1130
1131 #[test]
1133 fn a_non_doc_attribute_is_a_lex_error() {
1134 let input = TokenStream::from_str("#[foo]").unwrap();
1135 let mut lexer = LexLexer::new(input.into_iter());
1136 let token = lexer.next().unwrap();
1137 assert!(
1138 matches!(token, Token::Error { .. }),
1139 "expected Error, got {token:?}"
1140 );
1141 }
1142
1143 #[test]
1145 fn a_non_string_doc_value_is_a_lex_error() {
1146 let input = TokenStream::from_str("#[doc = 5]").unwrap();
1147 let mut lexer = LexLexer::new(input.into_iter());
1148 let token = lexer.next().unwrap();
1149 assert!(
1150 matches!(token, Token::Error { .. }),
1151 "expected Error, got {token:?}"
1152 );
1153 }
1154
1155 #[test]
1157 fn a_bare_hash_with_no_group_is_a_lex_error() {
1158 let input = TokenStream::from_str("# foo").unwrap();
1159 let mut lexer = LexLexer::new(input.into_iter());
1160 let token = lexer.next().unwrap();
1161 assert!(
1162 matches!(token, Token::Error { .. }),
1163 "expected Error, got {token:?}"
1164 );
1165 }
1166
1167 #[test]
1169 fn a_hash_led_group_with_the_wrong_delimiter_is_a_lex_error() {
1170 let input = TokenStream::from_str("#(foo)").unwrap();
1171 let mut lexer = LexLexer::new(input.into_iter());
1172 let token = lexer.next().unwrap();
1173 assert!(
1174 matches!(token, Token::Error { .. }),
1175 "expected Error, got {token:?}"
1176 );
1177 }
1178
1179 #[test]
1181 fn extra_tokens_after_the_doc_string_are_a_lex_error() {
1182 let input = TokenStream::from_str("#[doc = \"x\", extra]").unwrap();
1183 let mut lexer = LexLexer::new(input.into_iter());
1184 let token = lexer.next().unwrap();
1185 assert!(
1186 matches!(token, Token::Error { .. }),
1187 "expected Error, got {token:?}"
1188 );
1189 }
1190
1191 #[test]
1193 fn doc_comment_token_has_a_span() {
1194 let input = TokenStream::from_str("/// x").unwrap();
1195 let mut lexer = LexLexer::new(input.into_iter());
1196 let token = lexer.next().unwrap();
1197 let span = HasSpan::span(&token);
1198 assert!(!span.source_text().unwrap_or_default().is_empty());
1199 }
1200
1201 #[test]
1203 fn walrus_operator_lexes_as_one_compound_token() {
1204 let stream: TokenStream = ":=".parse().unwrap();
1205 let mut lexer = LexLexer::new(stream.into_iter());
1206 let token = lexer.next().expect("one token");
1207 assert!(matches!(
1208 token,
1209 Token::Punct { ref op, .. } if op == ":="
1210 ));
1211 assert!(lexer.next().is_none());
1212 }
1213
1214 #[test]
1216 fn colon_followed_by_non_equals_stays_two_separate_tokens() {
1217 let stream: TokenStream = ": x".parse().unwrap();
1218 let mut lexer = LexLexer::new(stream.into_iter());
1219 let first = lexer.next().expect("colon token");
1220 assert!(matches!(first, Token::Punct { ref op, .. } if op == ":"));
1221 assert!(matches!(lexer.next(), Some(Token::Identifier(id)) if id == "x"));
1222 assert!(lexer.next().is_none());
1223 }
1224
1225 #[test]
1227 fn digit_followed_by_double_dot_does_not_lex_as_a_float_literal() {
1228 let input = TokenStream::from_str("5..10").unwrap();
1229 let mut lexer = LexLexer::new(input.into_iter());
1230 match lexer.next() {
1231 Some(Token::Literal(Lit::Int(lit))) => {
1232 assert_eq!(lit.base10_parse::<i32>().unwrap(), 5)
1233 }
1234 other => panic!("expected an integer literal, got {other:?}"),
1235 }
1236 assert!(matches!(lexer.next(), Some(Token::Punct { op, .. }) if op == ".."));
1237 assert!(matches!(lexer.next(), Some(Token::Literal(Lit::Int(lit)))
1238 if lit.base10_parse::<i32>().unwrap() == 10));
1239 assert!(lexer.next().is_none());
1240 }
1241
1242 #[test]
1244 fn double_dot_combines_into_two_char_punct() {
1245 let stream: TokenStream = "..".parse().unwrap();
1246 let mut lexer = LexLexer::new(stream.into_iter());
1247 let tok = lexer.next().expect("one token");
1248 assert!(
1249 matches!(
1250 tok,
1251 Token::Punct {
1252 op: PunctOp::Two(['.', '.']),
1253 ..
1254 }
1255 ),
1256 "expected PunctOp::Two(['.', '.']), got {tok:?}"
1257 );
1258 assert!(lexer.next().is_none(), "expected no more tokens");
1259 }
1260
1261 #[test]
1263 fn double_dot_equals_combines_into_three_char_punct() {
1264 let stream: TokenStream = "..=".parse().unwrap();
1265 let mut lexer = LexLexer::new(stream.into_iter());
1266 let tok = lexer.next().expect("one token");
1267 assert!(
1268 matches!(
1269 tok,
1270 Token::Punct {
1271 op: PunctOp::Three(['.', '.', '=']),
1272 ..
1273 }
1274 ),
1275 "expected PunctOp::Three(['.', '.', '=']), got {tok:?}"
1276 );
1277 assert!(lexer.next().is_none(), "expected no more tokens");
1278 }
1279
1280 #[test]
1282 fn double_dot_not_followed_by_equals_stays_two_char_and_does_not_consume_the_next_token() {
1283 let stream: TokenStream = "..5".parse().unwrap();
1284 let mut lexer = LexLexer::new(stream.into_iter());
1285 let first = lexer.next().expect("dot-dot token");
1286 assert!(
1287 matches!(
1288 first,
1289 Token::Punct {
1290 op: PunctOp::Two(['.', '.']),
1291 ..
1292 }
1293 ),
1294 "expected PunctOp::Two(['.', '.']), got {first:?}"
1295 );
1296 match lexer.next() {
1297 Some(Token::Literal(Lit::Int(lit))) => {
1298 assert_eq!(lit.base10_parse::<i32>().unwrap(), 5)
1299 }
1300 other => panic!("expected integer literal 5, got {other:?}"),
1301 }
1302 }
1303
1304 #[test]
1306 fn single_dot_before_a_digit_is_unaffected_by_range_combining() {
1307 let stream: TokenStream = ".0".parse().unwrap();
1310 let mut lexer = LexLexer::new(stream.into_iter());
1311 let first = lexer.next().expect("dot token");
1312 assert!(
1313 matches!(
1314 first,
1315 Token::Punct {
1316 op: PunctOp::One('.'),
1317 ..
1318 }
1319 ),
1320 "expected PunctOp::One('.'), got {first:?}"
1321 );
1322 assert!(matches!(lexer.next(), Some(Token::Literal(Lit::Int(lit)))
1323 if lit.base10_parse::<i32>().unwrap() == 0));
1324 assert!(lexer.next().is_none());
1325 }
1326}