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(super) fn mode(&self) -> yash_env::parser::Mode {
270 self.lexer.mode()
271 }
272
273 pub async fn peek_token(&mut self) -> Result<&Token> {
277 self.require_token().await;
278 self.token.as_ref().unwrap().as_ref().map_err(|e| e.clone())
279 }
280
281 pub async fn take_token_raw(&mut self) -> Result<Token> {
290 self.require_token().await;
291 self.token.take().unwrap()
292 }
293
294 fn substitute_alias(&mut self, token: Token, is_command_name: bool) -> Rec<Token> {
297 if !self.aliases.is_empty()
299 && let Token(_) = token.id
300 && let Some(name) = token.word.to_string_if_literal()
301 && !token.word.location.code.source.is_alias_for(&name)
302 && let Some(alias) = self.aliases.look_up(&name)
303 && (is_command_name
304 || alias.global
305 || self.lexer.is_after_blank_ending_alias(token.index))
306 {
307 self.lexer.substitute_alias(token.index, &alias);
308 return Rec::AliasSubstituted;
309 }
310
311 Rec::Parsed(token)
312 }
313
314 pub async fn take_token_manual(&mut self, is_command_name: bool) -> Result<Rec<Token>> {
339 let token = self.take_token_raw().await?;
340 Ok(self.substitute_alias(token, is_command_name))
341 }
342
343 pub async fn take_token_auto(&mut self, keywords: &[Keyword]) -> Result<Token> {
356 loop {
357 let token = self.take_token_raw().await?;
358 if let Token(Some(keyword)) = token.id
359 && keywords.contains(&keyword)
360 {
361 return Ok(token);
362 }
363 if let Rec::Parsed(token) = self.substitute_alias(token, false) {
364 return Ok(token);
365 }
366 }
367 }
368
369 pub async fn has_blank(&mut self) -> Result<bool> {
385 assert!(self.token.is_none(), "There should be no pending token");
386 let c = self.lexer.peek_char().await?;
387 Ok(c.is_some_and(is_blank))
388 }
389
390 pub fn memorize_unread_here_doc(&mut self, here_doc: Rc<HereDoc>) {
395 self.unread_here_docs.push(here_doc)
396 }
397
398 pub async fn here_doc_contents(&mut self) -> Result<()> {
412 assert!(
413 self.token.is_none(),
414 "No token must be peeked before reading here-doc contents"
415 );
416
417 for here_doc in self.unread_here_docs.drain(..) {
418 self.lexer.here_doc_content(&here_doc).await?;
419 }
420
421 Ok(())
422 }
423
424 pub fn ensure_no_unread_here_doc(&self) -> Result<()> {
428 match self.unread_here_docs.first() {
429 None => Ok(()),
430 Some(here_doc) => Err(Error {
431 cause: SyntaxError::MissingHereDocContent.into(),
432 location: here_doc.delimiter.location.clone(),
433 }),
434 }
435 }
436
437 pub(super) fn word_names_declaration_utility(&self, word: &Word) -> Option<bool> {
441 if let Some(name) = word.to_string_if_literal() {
442 self.decl_utils.is_declaration_utility(&name)
443 } else {
444 Some(false)
445 }
446 }
447}
448
449#[allow(
450 clippy::bool_assert_comparison,
451 reason = "to make the expected values clearer"
452)]
453#[cfg(test)]
454mod tests {
455 use super::*;
456 use crate::alias::AliasSet;
457 use crate::alias::HashEntry;
458 use crate::source::Location;
459 use futures_util::FutureExt as _;
460 use std::assert_matches;
461 use std::cell::OnceCell;
462
463 #[test]
464 fn parser_take_token_manual_successful_substitution() {
465 let mut lexer = Lexer::with_code("X");
466 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
467 let mut aliases = AliasSet::new();
468 aliases.insert(HashEntry::new(
469 "X".to_string(),
470 "x".to_string(),
471 false,
472 Location::dummy("?"),
473 ));
474 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
475
476 let result = parser.take_token_manual(true).now_or_never().unwrap();
477 assert_matches!(result, Ok(Rec::AliasSubstituted));
478
479 let result = parser.take_token_manual(true).now_or_never().unwrap();
480 let token = result.unwrap().unwrap();
481 assert_eq!(token.to_string(), "x");
482 }
483
484 #[test]
485 fn parser_take_token_manual_not_command_name() {
486 let mut lexer = Lexer::with_code("X");
487 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
488 let mut aliases = AliasSet::new();
489 aliases.insert(HashEntry::new(
490 "X".to_string(),
491 "x".to_string(),
492 false,
493 Location::dummy("?"),
494 ));
495 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
496
497 let result = parser.take_token_manual(false).now_or_never().unwrap();
498 let token = result.unwrap().unwrap();
499 assert_eq!(token.to_string(), "X");
500 }
501
502 #[test]
503 fn parser_take_token_manual_not_literal() {
504 let mut lexer = Lexer::with_code(r"\X");
505 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
506 let mut aliases = AliasSet::new();
507 aliases.insert(HashEntry::new(
508 "X".to_string(),
509 "x".to_string(),
510 false,
511 Location::dummy("?"),
512 ));
513 aliases.insert(HashEntry::new(
514 r"\X".to_string(),
515 "quoted".to_string(),
516 false,
517 Location::dummy("?"),
518 ));
519 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
520
521 let result = parser.take_token_manual(true).now_or_never().unwrap();
522 let token = result.unwrap().unwrap();
523 assert_eq!(token.to_string(), r"\X");
524 }
525
526 #[test]
527 fn parser_take_token_manual_operator() {
528 let mut lexer = Lexer::with_code(";");
529 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
530 let mut aliases = AliasSet::new();
531 aliases.insert(HashEntry::new(
532 ";".to_string(),
533 "x".to_string(),
534 false,
535 Location::dummy("?"),
536 ));
537 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
538
539 let result = parser.take_token_manual(true).now_or_never().unwrap();
540 let token = result.unwrap().unwrap();
541 assert_eq!(token.id, Operator(super::super::lex::Operator::Semicolon));
542 assert_eq!(token.word.to_string_if_literal().unwrap(), ";");
543 }
544
545 #[test]
546 fn parser_take_token_manual_no_match() {
547 let mut lexer = Lexer::with_code("X");
548 let mut parser = Parser::new(&mut lexer);
549
550 let result = parser.take_token_manual(true).now_or_never().unwrap();
551 let token = result.unwrap().unwrap();
552 assert_eq!(token.to_string(), "X");
553 }
554
555 #[test]
556 fn parser_take_token_manual_recursive_substitution() {
557 let mut lexer = Lexer::with_code("X");
558 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
559 let mut aliases = AliasSet::new();
560 aliases.insert(HashEntry::new(
561 "X".to_string(),
562 "Y x".to_string(),
563 false,
564 Location::dummy("?"),
565 ));
566 aliases.insert(HashEntry::new(
567 "Y".to_string(),
568 "X y".to_string(),
569 false,
570 Location::dummy("?"),
571 ));
572 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
573
574 let result = parser.take_token_manual(true).now_or_never().unwrap();
575 assert_matches!(result, Ok(Rec::AliasSubstituted));
576
577 let result = parser.take_token_manual(true).now_or_never().unwrap();
578 assert_matches!(result, Ok(Rec::AliasSubstituted));
579
580 let result = parser.take_token_manual(true).now_or_never().unwrap();
581 let token = result.unwrap().unwrap();
582 assert_eq!(token.to_string(), "X");
583
584 let result = parser.take_token_manual(true).now_or_never().unwrap();
585 let token = result.unwrap().unwrap();
586 assert_eq!(token.to_string(), "y");
587
588 let rec = parser.take_token_manual(true).now_or_never().unwrap();
589 let token = rec.unwrap().unwrap();
590 assert_eq!(token.to_string(), "x");
591 }
592
593 #[test]
594 fn parser_take_token_manual_after_blank_ending_substitution() {
595 let mut lexer = Lexer::with_code("X\tY");
596 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
597 let mut aliases = AliasSet::new();
598 aliases.insert(HashEntry::new(
599 "X".to_string(),
600 " X ".to_string(),
601 false,
602 Location::dummy("?"),
603 ));
604 aliases.insert(HashEntry::new(
605 "Y".to_string(),
606 "y".to_string(),
607 false,
608 Location::dummy("?"),
609 ));
610 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
611
612 let result = parser.take_token_manual(true).now_or_never().unwrap();
613 assert_matches!(result, Ok(Rec::AliasSubstituted));
614
615 let result = parser.take_token_manual(true).now_or_never().unwrap();
616 let token = result.unwrap().unwrap();
617 assert_eq!(token.to_string(), "X");
618
619 let result = parser.take_token_manual(false).now_or_never().unwrap();
620 assert_matches!(result, Ok(Rec::AliasSubstituted));
621
622 let result = parser.take_token_manual(false).now_or_never().unwrap();
623 let token = result.unwrap().unwrap();
624 assert_eq!(token.to_string(), "y");
625 }
626
627 #[test]
628 fn parser_take_token_manual_not_after_blank_ending_substitution() {
629 let mut lexer = Lexer::with_code("X\tY");
630 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
631 let mut aliases = AliasSet::new();
632 aliases.insert(HashEntry::new(
633 "X".to_string(),
634 " X".to_string(),
635 false,
636 Location::dummy("?"),
637 ));
638 aliases.insert(HashEntry::new(
639 "Y".to_string(),
640 "y".to_string(),
641 false,
642 Location::dummy("?"),
643 ));
644 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
645
646 let result = parser.take_token_manual(true).now_or_never().unwrap();
647 assert_matches!(result, Ok(Rec::AliasSubstituted));
648
649 let result = parser.take_token_manual(true).now_or_never().unwrap();
650 let token = result.unwrap().unwrap();
651 assert_eq!(token.to_string(), "X");
652
653 let result = parser.take_token_manual(false).now_or_never().unwrap();
654 let token = result.unwrap().unwrap();
655 assert_eq!(token.to_string(), "Y");
656 }
657
658 #[test]
659 fn parser_take_token_manual_global() {
660 let mut lexer = Lexer::with_code("X");
661 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
662 let mut aliases = AliasSet::new();
663 aliases.insert(HashEntry::new(
664 "X".to_string(),
665 "x".to_string(),
666 true,
667 Location::dummy("?"),
668 ));
669 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
670
671 let result = parser.take_token_manual(false).now_or_never().unwrap();
672 assert_matches!(result, Ok(Rec::AliasSubstituted));
673
674 let result = parser.take_token_manual(false).now_or_never().unwrap();
675 let token = result.unwrap().unwrap();
676 assert_eq!(token.to_string(), "x");
677 }
678
679 #[test]
680 fn parser_take_token_auto_non_keyword() {
681 let mut lexer = Lexer::with_code("X");
682 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
683 let mut aliases = AliasSet::new();
684 aliases.insert(HashEntry::new(
685 "X".to_string(),
686 "x".to_string(),
687 true,
688 Location::dummy("?"),
689 ));
690 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
691
692 let token = parser.take_token_auto(&[]).now_or_never().unwrap().unwrap();
693 assert_eq!(token.to_string(), "x");
694 }
695
696 #[test]
697 fn parser_take_token_auto_keyword_matched() {
698 let mut lexer = Lexer::with_code("if");
699 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
700 let mut aliases = AliasSet::new();
701 aliases.insert(HashEntry::new(
702 "if".to_string(),
703 "x".to_string(),
704 true,
705 Location::dummy("?"),
706 ));
707 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
708
709 let token = parser
710 .take_token_auto(&[Keyword::If])
711 .now_or_never()
712 .unwrap()
713 .unwrap();
714 assert_eq!(token.to_string(), "if");
715 }
716
717 #[test]
718 fn parser_take_token_auto_keyword_unmatched() {
719 let mut lexer = Lexer::with_code("if");
720 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
721 let mut aliases = AliasSet::new();
722 aliases.insert(HashEntry::new(
723 "if".to_string(),
724 "x".to_string(),
725 true,
726 Location::dummy("?"),
727 ));
728 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
729
730 let token = parser.take_token_auto(&[]).now_or_never().unwrap().unwrap();
731 assert_eq!(token.to_string(), "x");
732 }
733
734 #[test]
735 fn parser_take_token_auto_alias_substitution_to_keyword_matched() {
736 let mut lexer = Lexer::with_code("X");
737 #[allow(clippy::mutable_key_type, reason = "AliasSet is defined as such")]
738 let mut aliases = AliasSet::new();
739 aliases.insert(HashEntry::new(
740 "X".to_string(),
741 "if".to_string(),
742 true,
743 Location::dummy("?"),
744 ));
745 aliases.insert(HashEntry::new(
746 "if".to_string(),
747 "x".to_string(),
748 true,
749 Location::dummy("?"),
750 ));
751 let mut parser = Parser::config().aliases(&aliases).input(&mut lexer);
752
753 let token = parser
754 .take_token_auto(&[Keyword::If])
755 .now_or_never()
756 .unwrap()
757 .unwrap();
758 assert_eq!(token.to_string(), "if");
759 }
760
761 #[test]
762 fn parser_has_blank_true() {
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(true));
767 }
768
769 #[test]
770 fn parser_has_blank_false() {
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_eof() {
779 let mut lexer = Lexer::with_code("");
780 let mut parser = Parser::new(&mut lexer);
781 let result = parser.has_blank().now_or_never().unwrap();
782 assert_eq!(result, Ok(false));
783 }
784
785 #[test]
786 fn parser_has_blank_true_with_line_continuations() {
787 let mut lexer = Lexer::with_code("\\\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(true));
791 }
792
793 #[test]
794 fn parser_has_blank_false_with_line_continuations() {
795 let mut lexer = Lexer::with_code("\\\n\\\n\\\n(");
796 let mut parser = Parser::new(&mut lexer);
797 let result = parser.has_blank().now_or_never().unwrap();
798 assert_eq!(result, Ok(false));
799 }
800
801 #[test]
802 #[should_panic(expected = "There should be no pending token")]
803 fn parser_has_blank_with_pending_token() {
804 let mut lexer = Lexer::with_code("foo");
805 let mut parser = Parser::new(&mut lexer);
806 parser.peek_token().now_or_never().unwrap().unwrap();
807 let _ = parser.has_blank().now_or_never().unwrap();
808 }
809
810 #[test]
811 fn parser_reading_no_here_doc_contents() {
812 let mut lexer = Lexer::with_code("X");
813 let mut parser = Parser::new(&mut lexer);
814 parser.here_doc_contents().now_or_never().unwrap().unwrap();
815
816 let location = lexer.location().now_or_never().unwrap().unwrap();
817 assert_eq!(location.code.start_line_number.get(), 1);
818 assert_eq!(location.range, 0..1);
819 }
820
821 #[test]
822 fn parser_reading_one_here_doc_content() {
823 let delimiter = "END".parse().unwrap();
824
825 let mut lexer = Lexer::with_code("END\nX");
826 let mut parser = Parser::new(&mut lexer);
827 let remove_tabs = false;
828 let here_doc = Rc::new(HereDoc {
829 delimiter,
830 remove_tabs,
831 content: OnceCell::new(),
832 });
833 parser.memorize_unread_here_doc(Rc::clone(&here_doc));
834 parser.here_doc_contents().now_or_never().unwrap().unwrap();
835 assert_eq!(here_doc.delimiter.to_string(), "END");
836 assert_eq!(here_doc.remove_tabs, remove_tabs);
837 assert_eq!(here_doc.content.get().unwrap().0, []);
838
839 let location = lexer.location().now_or_never().unwrap().unwrap();
840 assert_eq!(location.code.start_line_number.get(), 1);
841 assert_eq!(location.range, 4..5);
842 }
843
844 #[test]
845 fn parser_reading_many_here_doc_contents() {
846 let delimiter1 = "ONE".parse().unwrap();
847 let delimiter2 = "TWO".parse().unwrap();
848 let delimiter3 = "THREE".parse().unwrap();
849
850 let mut lexer = Lexer::with_code("1\nONE\nTWO\n3\nTHREE\nX");
851 let mut parser = Parser::new(&mut lexer);
852 let here_doc1 = Rc::new(HereDoc {
853 delimiter: delimiter1,
854 remove_tabs: false,
855 content: OnceCell::new(),
856 });
857 parser.memorize_unread_here_doc(Rc::clone(&here_doc1));
858 let here_doc2 = Rc::new(HereDoc {
859 delimiter: delimiter2,
860 remove_tabs: true,
861 content: OnceCell::new(),
862 });
863 parser.memorize_unread_here_doc(Rc::clone(&here_doc2));
864 let here_doc3 = Rc::new(HereDoc {
865 delimiter: delimiter3,
866 remove_tabs: false,
867 content: OnceCell::new(),
868 });
869 parser.memorize_unread_here_doc(Rc::clone(&here_doc3));
870 parser.here_doc_contents().now_or_never().unwrap().unwrap();
871 assert_eq!(here_doc1.delimiter.to_string(), "ONE");
872 assert_eq!(here_doc1.remove_tabs, false);
873 assert_eq!(here_doc1.content.get().unwrap().to_string(), "1\n");
874 assert_eq!(here_doc2.delimiter.to_string(), "TWO");
875 assert_eq!(here_doc2.remove_tabs, true);
876 assert_eq!(here_doc2.content.get().unwrap().to_string(), "");
877 assert_eq!(here_doc3.delimiter.to_string(), "THREE");
878 assert_eq!(here_doc3.remove_tabs, false);
879 assert_eq!(here_doc3.content.get().unwrap().to_string(), "3\n");
880 }
881
882 #[test]
883 fn parser_reading_here_doc_contents_twice() {
884 let delimiter1 = "ONE".parse().unwrap();
885 let delimiter2 = "TWO".parse().unwrap();
886
887 let mut lexer = Lexer::with_code("1\nONE\n2\nTWO\n");
888 let mut parser = Parser::new(&mut lexer);
889 let here_doc1 = Rc::new(HereDoc {
890 delimiter: delimiter1,
891 remove_tabs: false,
892 content: OnceCell::new(),
893 });
894 parser.memorize_unread_here_doc(Rc::clone(&here_doc1));
895 parser.here_doc_contents().now_or_never().unwrap().unwrap();
896 let here_doc2 = Rc::new(HereDoc {
897 delimiter: delimiter2,
898 remove_tabs: true,
899 content: OnceCell::new(),
900 });
901 parser.memorize_unread_here_doc(Rc::clone(&here_doc2));
902 parser.here_doc_contents().now_or_never().unwrap().unwrap();
903 assert_eq!(here_doc1.delimiter.to_string(), "ONE");
904 assert_eq!(here_doc1.remove_tabs, false);
905 assert_eq!(here_doc1.content.get().unwrap().to_string(), "1\n");
906 assert_eq!(here_doc2.delimiter.to_string(), "TWO");
907 assert_eq!(here_doc2.remove_tabs, true);
908 assert_eq!(here_doc2.content.get().unwrap().to_string(), "2\n");
909 }
910
911 #[test]
912 #[should_panic(expected = "No token must be peeked before reading here-doc contents")]
913 fn parser_here_doc_contents_must_be_called_without_pending_token() {
914 let mut lexer = Lexer::with_code("X");
915 let mut parser = Parser::new(&mut lexer);
916 parser.peek_token().now_or_never().unwrap().unwrap();
917 parser.here_doc_contents().now_or_never().unwrap().unwrap();
918 }
919}