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