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