1use super::error::Error;
23use super::error::SyntaxError;
24use super::lex::Keyword;
25use super::lex::Lexer;
26use super::lex::Token;
27use super::lex::TokenId::*;
28use crate::alias::Glossary;
29use crate::parser::lex::is_blank;
30use crate::syntax::HereDoc;
31use crate::syntax::MaybeLiteral as _;
32use crate::syntax::Word;
33use std::rc::Rc;
34
35pub type Result<T> = std::result::Result<T, Error>;
37
38#[derive(Copy, Clone, Debug, Eq, PartialEq)]
57pub enum Rec<T> {
58 AliasSubstituted,
60 Parsed(T),
62}
63
64impl<T> Rec<T> {
65 pub fn is_alias_substituted(&self) -> bool {
67 match self {
68 Rec::AliasSubstituted => true,
69 Rec::Parsed(_) => false,
70 }
71 }
72
73 pub fn unwrap(self) -> T {
79 match self {
80 Rec::AliasSubstituted => panic!("Rec::AliasSubstituted cannot be unwrapped"),
81 Rec::Parsed(v) => v,
82 }
83 }
84
85 pub fn map<U, F>(self, f: F) -> Result<Rec<U>>
87 where
88 F: FnOnce(T) -> Result<U>,
89 {
90 match self {
91 Rec::AliasSubstituted => Ok(Rec::AliasSubstituted),
92 Rec::Parsed(t) => Ok(Rec::Parsed(f(t)?)),
93 }
94 }
95}
96
97#[derive(Debug)]
107#[must_use = "Config must be used to create a parser"]
108pub struct Config<'a> {
109 aliases: &'a dyn crate::alias::Glossary,
111
112 decl_utils: &'a dyn crate::decl_util::Glossary,
114}
115
116impl<'a> Config<'a> {
117 pub fn new() -> Self {
121 Self {
122 aliases: &crate::alias::EmptyGlossary,
123 decl_utils: &crate::decl_util::PosixGlossary,
124 }
125 }
126
127 #[inline]
132 pub fn aliases(&mut self, aliases: &'a dyn Glossary) -> &mut Self {
133 self.aliases = aliases;
134 self
135 }
136
137 #[inline]
156 pub fn declaration_utilities(
157 &mut self,
158 decl_utils: &'a dyn crate::decl_util::Glossary,
159 ) -> &mut Self {
160 self.decl_utils = decl_utils;
161 self
162 }
163
164 pub fn input<'b>(&self, lexer: &'a mut Lexer<'b>) -> Parser<'a, 'b> {
166 Parser {
167 lexer,
168 aliases: self.aliases,
169 decl_utils: self.decl_utils,
170 token: None,
171 unread_here_docs: Vec::new(),
172 }
173 }
174}
175
176impl Default for Config<'_> {
177 fn default() -> Self {
178 Self::new()
179 }
180}
181
182#[derive(Debug)]
208#[must_use = "Parser must be used to parse syntax"]
209pub struct Parser<'a, 'b> {
210 lexer: &'a mut Lexer<'b>,
212
213 aliases: &'a dyn crate::alias::Glossary,
215
216 decl_utils: &'a dyn crate::decl_util::Glossary,
218
219 token: Option<Result<Token>>,
224
225 unread_here_docs: Vec<Rc<HereDoc>>,
231}
232
233impl<'a, 'b> Parser<'a, 'b> {
234 #[inline(always)]
240 pub fn config() -> Config<'a> {
241 Config::new()
242 }
243
244 pub fn new(lexer: &'a mut Lexer<'b>) -> Parser<'a, 'b> {
249 Self::config().input(lexer)
250 }
251
252 async fn require_token(&mut self) {
254 #[allow(clippy::question_mark, reason = "false positive")]
255 if self.token.is_none() {
257 self.token = Some(if let Err(e) = self.lexer.skip_blanks_and_comment().await {
258 Err(e)
259 } else {
260 self.lexer.token().await
261 });
262 }
263 }
264
265 pub async fn peek_token(&mut self) -> Result<&Token> {
269 self.require_token().await;
270 self.token.as_ref().unwrap().as_ref().map_err(|e| e.clone())
271 }
272
273 pub async fn take_token_raw(&mut self) -> Result<Token> {
282 self.require_token().await;
283 self.token.take().unwrap()
284 }
285
286 fn substitute_alias(&mut self, token: Token, is_command_name: bool) -> Rec<Token> {
289 if !self.aliases.is_empty()
291 && let Token(_) = token.id
292 && let Some(name) = token.word.to_string_if_literal()
293 && !token.word.location.code.source.is_alias_for(&name)
294 && let Some(alias) = self.aliases.look_up(&name)
295 && (is_command_name
296 || alias.global
297 || self.lexer.is_after_blank_ending_alias(token.index))
298 {
299 self.lexer.substitute_alias(token.index, &alias);
300 return Rec::AliasSubstituted;
301 }
302
303 Rec::Parsed(token)
304 }
305
306 pub async fn take_token_manual(&mut self, is_command_name: bool) -> Result<Rec<Token>> {
331 let token = self.take_token_raw().await?;
332 Ok(self.substitute_alias(token, is_command_name))
333 }
334
335 pub async fn take_token_auto(&mut self, keywords: &[Keyword]) -> Result<Token> {
348 loop {
349 let token = self.take_token_raw().await?;
350 if let Token(Some(keyword)) = token.id
351 && keywords.contains(&keyword)
352 {
353 return Ok(token);
354 }
355 if let Rec::Parsed(token) = self.substitute_alias(token, false) {
356 return Ok(token);
357 }
358 }
359 }
360
361 pub async fn has_blank(&mut self) -> Result<bool> {
377 assert!(self.token.is_none(), "There should be no pending token");
378 let c = self.lexer.peek_char().await?;
379 Ok(c.is_some_and(is_blank))
380 }
381
382 pub fn memorize_unread_here_doc(&mut self, here_doc: Rc<HereDoc>) {
387 self.unread_here_docs.push(here_doc)
388 }
389
390 pub async fn here_doc_contents(&mut self) -> Result<()> {
404 assert!(
405 self.token.is_none(),
406 "No token must be peeked before reading here-doc contents"
407 );
408
409 for here_doc in self.unread_here_docs.drain(..) {
410 self.lexer.here_doc_content(&here_doc).await?;
411 }
412
413 Ok(())
414 }
415
416 pub fn ensure_no_unread_here_doc(&self) -> Result<()> {
420 match self.unread_here_docs.first() {
421 None => Ok(()),
422 Some(here_doc) => Err(Error {
423 cause: SyntaxError::MissingHereDocContent.into(),
424 location: here_doc.delimiter.location.clone(),
425 }),
426 }
427 }
428
429 pub(super) fn word_names_declaration_utility(&self, word: &Word) -> Option<bool> {
433 if let Some(name) = word.to_string_if_literal() {
434 self.decl_utils.is_declaration_utility(&name)
435 } else {
436 Some(false)
437 }
438 }
439}
440
441#[allow(
442 clippy::bool_assert_comparison,
443 reason = "to make the expected values clearer"
444)]
445#[cfg(test)]
446mod tests {
447 use super::*;
448 use crate::alias::AliasSet;
449 use crate::alias::HashEntry;
450 use crate::source::Location;
451 use futures_util::FutureExt as _;
452 use std::assert_matches;
453 use std::cell::OnceCell;
454
455 #[test]
456 fn parser_take_token_manual_successful_substitution() {
457 let mut lexer = Lexer::with_code("X");
458 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
459 let mut aliases = AliasSet::new();
460 aliases.insert(HashEntry::new(
461 "X".to_string(),
462 "x".to_string(),
463 false,
464 Location::dummy("?"),
465 ));
466 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
467
468 let result = parser.take_token_manual(true).now_or_never().unwrap();
469 assert_matches!(result, Ok(Rec::AliasSubstituted));
470
471 let result = parser.take_token_manual(true).now_or_never().unwrap();
472 let token = result.unwrap().unwrap();
473 assert_eq!(token.to_string(), "x");
474 }
475
476 #[test]
477 fn parser_take_token_manual_not_command_name() {
478 let mut lexer = Lexer::with_code("X");
479 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
480 let mut aliases = AliasSet::new();
481 aliases.insert(HashEntry::new(
482 "X".to_string(),
483 "x".to_string(),
484 false,
485 Location::dummy("?"),
486 ));
487 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
488
489 let result = parser.take_token_manual(false).now_or_never().unwrap();
490 let token = result.unwrap().unwrap();
491 assert_eq!(token.to_string(), "X");
492 }
493
494 #[test]
495 fn parser_take_token_manual_not_literal() {
496 let mut lexer = Lexer::with_code(r"\X");
497 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
498 let mut aliases = AliasSet::new();
499 aliases.insert(HashEntry::new(
500 "X".to_string(),
501 "x".to_string(),
502 false,
503 Location::dummy("?"),
504 ));
505 aliases.insert(HashEntry::new(
506 r"\X".to_string(),
507 "quoted".to_string(),
508 false,
509 Location::dummy("?"),
510 ));
511 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
512
513 let result = parser.take_token_manual(true).now_or_never().unwrap();
514 let token = result.unwrap().unwrap();
515 assert_eq!(token.to_string(), r"\X");
516 }
517
518 #[test]
519 fn parser_take_token_manual_operator() {
520 let mut lexer = Lexer::with_code(";");
521 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
522 let mut aliases = AliasSet::new();
523 aliases.insert(HashEntry::new(
524 ";".to_string(),
525 "x".to_string(),
526 false,
527 Location::dummy("?"),
528 ));
529 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
530
531 let result = parser.take_token_manual(true).now_or_never().unwrap();
532 let token = result.unwrap().unwrap();
533 assert_eq!(token.id, Operator(super::super::lex::Operator::Semicolon));
534 assert_eq!(token.word.to_string_if_literal().unwrap(), ";");
535 }
536
537 #[test]
538 fn parser_take_token_manual_no_match() {
539 let mut lexer = Lexer::with_code("X");
540 let mut parser = Parser::new(&mut lexer);
541
542 let result = parser.take_token_manual(true).now_or_never().unwrap();
543 let token = result.unwrap().unwrap();
544 assert_eq!(token.to_string(), "X");
545 }
546
547 #[test]
548 fn parser_take_token_manual_recursive_substitution() {
549 let mut lexer = Lexer::with_code("X");
550 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
551 let mut aliases = AliasSet::new();
552 aliases.insert(HashEntry::new(
553 "X".to_string(),
554 "Y x".to_string(),
555 false,
556 Location::dummy("?"),
557 ));
558 aliases.insert(HashEntry::new(
559 "Y".to_string(),
560 "X y".to_string(),
561 false,
562 Location::dummy("?"),
563 ));
564 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
565
566 let result = parser.take_token_manual(true).now_or_never().unwrap();
567 assert_matches!(result, Ok(Rec::AliasSubstituted));
568
569 let result = parser.take_token_manual(true).now_or_never().unwrap();
570 assert_matches!(result, Ok(Rec::AliasSubstituted));
571
572 let result = parser.take_token_manual(true).now_or_never().unwrap();
573 let token = result.unwrap().unwrap();
574 assert_eq!(token.to_string(), "X");
575
576 let result = parser.take_token_manual(true).now_or_never().unwrap();
577 let token = result.unwrap().unwrap();
578 assert_eq!(token.to_string(), "y");
579
580 let rec = parser.take_token_manual(true).now_or_never().unwrap();
581 let token = rec.unwrap().unwrap();
582 assert_eq!(token.to_string(), "x");
583 }
584
585 #[test]
586 fn parser_take_token_manual_after_blank_ending_substitution() {
587 let mut lexer = Lexer::with_code("X\tY");
588 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
589 let mut aliases = AliasSet::new();
590 aliases.insert(HashEntry::new(
591 "X".to_string(),
592 " X ".to_string(),
593 false,
594 Location::dummy("?"),
595 ));
596 aliases.insert(HashEntry::new(
597 "Y".to_string(),
598 "y".to_string(),
599 false,
600 Location::dummy("?"),
601 ));
602 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
603
604 let result = parser.take_token_manual(true).now_or_never().unwrap();
605 assert_matches!(result, Ok(Rec::AliasSubstituted));
606
607 let result = parser.take_token_manual(true).now_or_never().unwrap();
608 let token = result.unwrap().unwrap();
609 assert_eq!(token.to_string(), "X");
610
611 let result = parser.take_token_manual(false).now_or_never().unwrap();
612 assert_matches!(result, Ok(Rec::AliasSubstituted));
613
614 let result = parser.take_token_manual(false).now_or_never().unwrap();
615 let token = result.unwrap().unwrap();
616 assert_eq!(token.to_string(), "y");
617 }
618
619 #[test]
620 fn parser_take_token_manual_not_after_blank_ending_substitution() {
621 let mut lexer = Lexer::with_code("X\tY");
622 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
623 let mut aliases = AliasSet::new();
624 aliases.insert(HashEntry::new(
625 "X".to_string(),
626 " X".to_string(),
627 false,
628 Location::dummy("?"),
629 ));
630 aliases.insert(HashEntry::new(
631 "Y".to_string(),
632 "y".to_string(),
633 false,
634 Location::dummy("?"),
635 ));
636 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
637
638 let result = parser.take_token_manual(true).now_or_never().unwrap();
639 assert_matches!(result, Ok(Rec::AliasSubstituted));
640
641 let result = parser.take_token_manual(true).now_or_never().unwrap();
642 let token = result.unwrap().unwrap();
643 assert_eq!(token.to_string(), "X");
644
645 let result = parser.take_token_manual(false).now_or_never().unwrap();
646 let token = result.unwrap().unwrap();
647 assert_eq!(token.to_string(), "Y");
648 }
649
650 #[test]
651 fn parser_take_token_manual_global() {
652 let mut lexer = Lexer::with_code("X");
653 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
654 let mut aliases = AliasSet::new();
655 aliases.insert(HashEntry::new(
656 "X".to_string(),
657 "x".to_string(),
658 true,
659 Location::dummy("?"),
660 ));
661 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
662
663 let result = parser.take_token_manual(false).now_or_never().unwrap();
664 assert_matches!(result, Ok(Rec::AliasSubstituted));
665
666 let result = parser.take_token_manual(false).now_or_never().unwrap();
667 let token = result.unwrap().unwrap();
668 assert_eq!(token.to_string(), "x");
669 }
670
671 #[test]
672 fn parser_take_token_auto_non_keyword() {
673 let mut lexer = Lexer::with_code("X");
674 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
675 let mut aliases = AliasSet::new();
676 aliases.insert(HashEntry::new(
677 "X".to_string(),
678 "x".to_string(),
679 true,
680 Location::dummy("?"),
681 ));
682 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
683
684 let token = parser.take_token_auto(&[]).now_or_never().unwrap().unwrap();
685 assert_eq!(token.to_string(), "x");
686 }
687
688 #[test]
689 fn parser_take_token_auto_keyword_matched() {
690 let mut lexer = Lexer::with_code("if");
691 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
692 let mut aliases = AliasSet::new();
693 aliases.insert(HashEntry::new(
694 "if".to_string(),
695 "x".to_string(),
696 true,
697 Location::dummy("?"),
698 ));
699 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
700
701 let token = parser
702 .take_token_auto(&[Keyword::If])
703 .now_or_never()
704 .unwrap()
705 .unwrap();
706 assert_eq!(token.to_string(), "if");
707 }
708
709 #[test]
710 fn parser_take_token_auto_keyword_unmatched() {
711 let mut lexer = Lexer::with_code("if");
712 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
713 let mut aliases = AliasSet::new();
714 aliases.insert(HashEntry::new(
715 "if".to_string(),
716 "x".to_string(),
717 true,
718 Location::dummy("?"),
719 ));
720 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
721
722 let token = parser.take_token_auto(&[]).now_or_never().unwrap().unwrap();
723 assert_eq!(token.to_string(), "x");
724 }
725
726 #[test]
727 fn parser_take_token_auto_alias_substitution_to_keyword_matched() {
728 let mut lexer = Lexer::with_code("X");
729 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
730 let mut aliases = AliasSet::new();
731 aliases.insert(HashEntry::new(
732 "X".to_string(),
733 "if".to_string(),
734 true,
735 Location::dummy("?"),
736 ));
737 aliases.insert(HashEntry::new(
738 "if".to_string(),
739 "x".to_string(),
740 true,
741 Location::dummy("?"),
742 ));
743 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
744
745 let token = parser
746 .take_token_auto(&[Keyword::If])
747 .now_or_never()
748 .unwrap()
749 .unwrap();
750 assert_eq!(token.to_string(), "if");
751 }
752
753 #[test]
754 fn parser_has_blank_true() {
755 let mut lexer = Lexer::with_code(" ");
756 let mut parser = Parser::new(&mut lexer);
757 let result = parser.has_blank().now_or_never().unwrap();
758 assert_eq!(result, Ok(true));
759 }
760
761 #[test]
762 fn parser_has_blank_false() {
763 let mut lexer = Lexer::with_code("(");
764 let mut parser = Parser::new(&mut lexer);
765 let result = parser.has_blank().now_or_never().unwrap();
766 assert_eq!(result, Ok(false));
767 }
768
769 #[test]
770 fn parser_has_blank_eof() {
771 let mut lexer = Lexer::with_code("");
772 let mut parser = Parser::new(&mut lexer);
773 let result = parser.has_blank().now_or_never().unwrap();
774 assert_eq!(result, Ok(false));
775 }
776
777 #[test]
778 fn parser_has_blank_true_with_line_continuations() {
779 let mut lexer = Lexer::with_code("\\\n\\\n ");
780 let mut parser = Parser::new(&mut lexer);
781 let result = parser.has_blank().now_or_never().unwrap();
782 assert_eq!(result, Ok(true));
783 }
784
785 #[test]
786 fn parser_has_blank_false_with_line_continuations() {
787 let mut lexer = Lexer::with_code("\\\n\\\n\\\n(");
788 let mut parser = Parser::new(&mut lexer);
789 let result = parser.has_blank().now_or_never().unwrap();
790 assert_eq!(result, Ok(false));
791 }
792
793 #[test]
794 #[should_panic(expected = "There should be no pending token")]
795 fn parser_has_blank_with_pending_token() {
796 let mut lexer = Lexer::with_code("foo");
797 let mut parser = Parser::new(&mut lexer);
798 parser.peek_token().now_or_never().unwrap().unwrap();
799 let _ = parser.has_blank().now_or_never().unwrap();
800 }
801
802 #[test]
803 fn parser_reading_no_here_doc_contents() {
804 let mut lexer = Lexer::with_code("X");
805 let mut parser = Parser::new(&mut lexer);
806 parser.here_doc_contents().now_or_never().unwrap().unwrap();
807
808 let location = lexer.location().now_or_never().unwrap().unwrap();
809 assert_eq!(location.code.start_line_number.get(), 1);
810 assert_eq!(location.range, 0..1);
811 }
812
813 #[test]
814 fn parser_reading_one_here_doc_content() {
815 let delimiter = "END".parse().unwrap();
816
817 let mut lexer = Lexer::with_code("END\nX");
818 let mut parser = Parser::new(&mut lexer);
819 let remove_tabs = false;
820 let here_doc = Rc::new(HereDoc {
821 delimiter,
822 remove_tabs,
823 content: OnceCell::new(),
824 });
825 parser.memorize_unread_here_doc(Rc::clone(&here_doc));
826 parser.here_doc_contents().now_or_never().unwrap().unwrap();
827 assert_eq!(here_doc.delimiter.to_string(), "END");
828 assert_eq!(here_doc.remove_tabs, remove_tabs);
829 assert_eq!(here_doc.content.get().unwrap().0, []);
830
831 let location = lexer.location().now_or_never().unwrap().unwrap();
832 assert_eq!(location.code.start_line_number.get(), 1);
833 assert_eq!(location.range, 4..5);
834 }
835
836 #[test]
837 fn parser_reading_many_here_doc_contents() {
838 let delimiter1 = "ONE".parse().unwrap();
839 let delimiter2 = "TWO".parse().unwrap();
840 let delimiter3 = "THREE".parse().unwrap();
841
842 let mut lexer = Lexer::with_code("1\nONE\nTWO\n3\nTHREE\nX");
843 let mut parser = Parser::new(&mut lexer);
844 let here_doc1 = Rc::new(HereDoc {
845 delimiter: delimiter1,
846 remove_tabs: false,
847 content: OnceCell::new(),
848 });
849 parser.memorize_unread_here_doc(Rc::clone(&here_doc1));
850 let here_doc2 = Rc::new(HereDoc {
851 delimiter: delimiter2,
852 remove_tabs: true,
853 content: OnceCell::new(),
854 });
855 parser.memorize_unread_here_doc(Rc::clone(&here_doc2));
856 let here_doc3 = Rc::new(HereDoc {
857 delimiter: delimiter3,
858 remove_tabs: false,
859 content: OnceCell::new(),
860 });
861 parser.memorize_unread_here_doc(Rc::clone(&here_doc3));
862 parser.here_doc_contents().now_or_never().unwrap().unwrap();
863 assert_eq!(here_doc1.delimiter.to_string(), "ONE");
864 assert_eq!(here_doc1.remove_tabs, false);
865 assert_eq!(here_doc1.content.get().unwrap().to_string(), "1\n");
866 assert_eq!(here_doc2.delimiter.to_string(), "TWO");
867 assert_eq!(here_doc2.remove_tabs, true);
868 assert_eq!(here_doc2.content.get().unwrap().to_string(), "");
869 assert_eq!(here_doc3.delimiter.to_string(), "THREE");
870 assert_eq!(here_doc3.remove_tabs, false);
871 assert_eq!(here_doc3.content.get().unwrap().to_string(), "3\n");
872 }
873
874 #[test]
875 fn parser_reading_here_doc_contents_twice() {
876 let delimiter1 = "ONE".parse().unwrap();
877 let delimiter2 = "TWO".parse().unwrap();
878
879 let mut lexer = Lexer::with_code("1\nONE\n2\nTWO\n");
880 let mut parser = Parser::new(&mut lexer);
881 let here_doc1 = Rc::new(HereDoc {
882 delimiter: delimiter1,
883 remove_tabs: false,
884 content: OnceCell::new(),
885 });
886 parser.memorize_unread_here_doc(Rc::clone(&here_doc1));
887 parser.here_doc_contents().now_or_never().unwrap().unwrap();
888 let here_doc2 = Rc::new(HereDoc {
889 delimiter: delimiter2,
890 remove_tabs: true,
891 content: OnceCell::new(),
892 });
893 parser.memorize_unread_here_doc(Rc::clone(&here_doc2));
894 parser.here_doc_contents().now_or_never().unwrap().unwrap();
895 assert_eq!(here_doc1.delimiter.to_string(), "ONE");
896 assert_eq!(here_doc1.remove_tabs, false);
897 assert_eq!(here_doc1.content.get().unwrap().to_string(), "1\n");
898 assert_eq!(here_doc2.delimiter.to_string(), "TWO");
899 assert_eq!(here_doc2.remove_tabs, true);
900 assert_eq!(here_doc2.content.get().unwrap().to_string(), "2\n");
901 }
902
903 #[test]
904 #[should_panic(expected = "No token must be peeked before reading here-doc contents")]
905 fn parser_here_doc_contents_must_be_called_without_pending_token() {
906 let mut lexer = Lexer::with_code("X");
907 let mut parser = Parser::new(&mut lexer);
908 parser.peek_token().now_or_never().unwrap().unwrap();
909 parser.here_doc_contents().now_or_never().unwrap().unwrap();
910 }
911}