1use crate::parser_state::{ParseAttempts, ParsingToken, RulesCallStack};
13use alloc::borrow::Cow;
14use alloc::borrow::ToOwned;
15use alloc::boxed::Box;
16use alloc::collections::{BTreeMap, BTreeSet};
17use alloc::format;
18use alloc::string::String;
19use alloc::string::ToString;
20use alloc::vec;
21use alloc::vec::Vec;
22use core::cmp;
23use core::fmt;
24use core::mem;
25
26use crate::position::Position;
27use crate::span::Span;
28use crate::RuleType;
29
30#[derive(Clone, Debug, Eq, Hash, PartialEq)]
32pub struct Error<R> {
33 pub variant: ErrorVariant<R>,
35 pub location: InputLocation,
37 pub line_col: LineColLocation,
39 inner: Box<ErrorInner<R>>,
40}
41
42#[derive(Clone, Debug, Eq, Hash, PartialEq)]
44struct ErrorInner<R> {
45 path: Option<String>,
46 line: String,
47 continued_line: Option<String>,
48 parse_attempts: Option<ParseAttempts<R>>,
49}
50
51impl<R: RuleType> core::error::Error for Error<R> {}
52
53#[derive(Clone, Debug, Eq, Hash, PartialEq)]
55pub enum ErrorVariant<R> {
56 ParsingError {
58 positives: Vec<R>,
60 negatives: Vec<R>,
62 },
63 CustomError {
65 message: String,
67 },
68}
69
70impl<R: RuleType> core::error::Error for ErrorVariant<R> {}
71
72#[derive(Clone, Debug, Eq, Hash, PartialEq)]
74pub enum InputLocation {
75 Pos(usize),
77 Span((usize, usize)),
79}
80
81#[derive(Clone, Debug, Eq, Hash, PartialEq)]
83pub enum LineColLocation {
84 Pos((usize, usize)),
86 Span((usize, usize), (usize, usize)),
88}
89
90impl From<Position<'_>> for LineColLocation {
91 fn from(value: Position<'_>) -> Self {
92 Self::Pos(value.line_col())
93 }
94}
95
96impl From<Span<'_>> for LineColLocation {
97 fn from(value: Span<'_>) -> Self {
98 let (start, end) = value.split();
99 Self::Span(start.line_col(), end.line_col())
100 }
101}
102
103pub type RuleToMessageFn<R> = Box<dyn Fn(&R) -> Option<String>>;
105pub type IsWhitespaceFn = Box<dyn Fn(String) -> bool>;
107
108impl ParsingToken {
109 pub fn is_whitespace(&self, is_whitespace: &IsWhitespaceFn) -> bool {
110 match self {
111 ParsingToken::Sensitive { token } => is_whitespace(token.clone()),
112 ParsingToken::Insensitive { token } => is_whitespace(token.clone()),
113 ParsingToken::Range { .. } => false,
114 ParsingToken::BuiltInRule => false,
115 }
116 }
117}
118
119impl<R: RuleType> ParseAttempts<R> {
120 fn tokens_helper_messages(
124 &self,
125 is_whitespace_fn: &IsWhitespaceFn,
126 spacing: &str,
127 ) -> Vec<String> {
128 let mut helper_messages = Vec::new();
129 let tokens_header_pairs = vec![
130 (self.expected_tokens(), "expected"),
131 (self.unexpected_tokens(), "unexpected"),
132 ];
133
134 for (tokens, header) in &tokens_header_pairs {
135 if tokens.is_empty() {
136 continue;
137 }
138
139 let mut helper_tokens_message = format!("{spacing}note: {header} ");
140 helper_tokens_message.push_str(if tokens.len() == 1 {
141 "token: "
142 } else {
143 "one of tokens: "
144 });
145
146 let expected_tokens_set: BTreeSet<String> = tokens
147 .iter()
148 .map(|token| {
149 if token.is_whitespace(is_whitespace_fn) {
150 String::from("WHITESPACE")
151 } else {
152 format!("`{}`", token)
153 }
154 })
155 .collect();
156
157 helper_tokens_message.push_str(
158 &expected_tokens_set
159 .iter()
160 .cloned()
161 .collect::<Vec<String>>()
162 .join(", "),
163 );
164 helper_messages.push(helper_tokens_message);
165 }
166
167 helper_messages
168 }
169}
170
171impl<R: RuleType> Error<R> {
172 pub fn new_from_pos(variant: ErrorVariant<R>, pos: Position<'_>) -> Error<R> {
199 let visualize_ws = pos.match_char('\n') || pos.match_char('\r');
200 let line_of = pos.line_of();
201 let line = if visualize_ws {
202 visualize_whitespace(line_of)
203 } else {
204 line_of.replace(&['\r', '\n'][..], "")
205 };
206 Error {
207 variant,
208 location: InputLocation::Pos(pos.pos()),
209 line_col: LineColLocation::Pos(pos.line_col()),
210 inner: Box::new(ErrorInner {
211 path: None,
212 line,
213 continued_line: None,
214 parse_attempts: None,
215 }),
216 }
217 }
218
219 pub(crate) fn new_from_pos_with_parsing_attempts(
222 variant: ErrorVariant<R>,
223 pos: Position<'_>,
224 parse_attempts: ParseAttempts<R>,
225 ) -> Error<R> {
226 let mut error = Self::new_from_pos(variant, pos);
227 error.inner.parse_attempts = Some(parse_attempts);
228 error
229 }
230
231 pub fn new_from_span(variant: ErrorVariant<R>, span: Span<'_>) -> Error<R> {
260 let end = span.end_pos();
261 let mut end_line_col = end.line_col();
262 if end_line_col.1 == 1 {
264 let mut visual_end = end;
265 visual_end.skip_back(1);
266 let lc = visual_end.line_col();
267 end_line_col = (lc.0, lc.1 + 1);
268 };
269
270 let mut line_iter = span.lines();
271 let sl = line_iter.next().unwrap_or("");
272 let mut chars = span.as_str().chars();
273 let visualize_ws = matches!(chars.next(), Some('\n') | Some('\r'))
274 || matches!(chars.last(), Some('\n') | Some('\r'));
275 let start_line = if visualize_ws {
276 visualize_whitespace(sl)
277 } else {
278 sl.to_owned().replace(&['\r', '\n'][..], "")
279 };
280 let ll = line_iter.last();
281 let continued_line = if visualize_ws {
282 ll.map(str::to_owned)
283 } else {
284 ll.map(visualize_whitespace)
285 };
286
287 Error {
288 variant,
289 location: InputLocation::Span((span.start(), end.pos())),
290 line_col: LineColLocation::Span(span.start_pos().line_col(), end_line_col),
291 inner: Box::new(ErrorInner {
292 path: None,
293 line: start_line,
294 continued_line,
295 parse_attempts: None,
296 }),
297 }
298 }
299
300 pub fn with_path(mut self, path: &str) -> Error<R> {
325 self.inner.path = Some(path.to_owned());
326
327 self
328 }
329
330 pub fn path(&self) -> Option<&str> {
356 self.inner.path.as_deref()
357 }
358
359 pub fn line(&self) -> &str {
361 self.inner.line.as_str()
362 }
363
364 pub fn renamed_rules<F>(mut self, f: F) -> Error<R>
400 where
401 F: FnMut(&R) -> String,
402 {
403 let variant = match self.variant {
404 ErrorVariant::ParsingError {
405 positives,
406 negatives,
407 } => {
408 let message = Error::parsing_error_message(&positives, &negatives, f);
409 ErrorVariant::CustomError { message }
410 }
411 variant => variant,
412 };
413
414 self.variant = variant;
415
416 self
417 }
418
419 pub fn parse_attempts(&self) -> Option<ParseAttempts<R>> {
422 self.inner.parse_attempts.clone()
423 }
424
425 pub fn parse_attempts_error(
428 &self,
429 input: &str,
430 rule_to_message: &RuleToMessageFn<R>,
431 is_whitespace: &IsWhitespaceFn,
432 ) -> Option<Error<R>> {
433 let attempts = if let Some(ref parse_attempts) = self.inner.parse_attempts {
434 parse_attempts.clone()
435 } else {
436 return None;
437 };
438
439 let spacing = self.spacing() + " ";
440 let error_position = attempts.max_position;
441 let message = {
442 let mut help_lines: Vec<String> = Vec::new();
443 help_lines.push(String::from("error: parsing error occurred."));
444
445 for tokens_helper_message in attempts.tokens_helper_messages(is_whitespace, &spacing) {
447 help_lines.push(tokens_helper_message);
448 }
449
450 let call_stacks = attempts.call_stacks();
451 let mut call_stacks_parents_groups: BTreeMap<Option<R>, Vec<RulesCallStack<R>>> =
454 BTreeMap::new();
455 for call_stack in call_stacks {
456 call_stacks_parents_groups
457 .entry(call_stack.parent)
458 .or_default()
459 .push(call_stack);
460 }
461
462 for (group_parent, group) in call_stacks_parents_groups {
463 if let Some(parent_rule) = group_parent {
464 let mut contains_meaningful_info = false;
465 help_lines.push(format!(
466 "{spacing}help: {}",
467 if let Some(message) = rule_to_message(&parent_rule) {
468 contains_meaningful_info = true;
469 message
470 } else {
471 String::from("[Unknown parent rule]")
472 }
473 ));
474 for call_stack in group {
475 if let Some(r) = call_stack.deepest.get_rule() {
476 if let Some(message) = rule_to_message(r) {
477 contains_meaningful_info = true;
478 help_lines.push(format!("{spacing} - {message}"));
479 }
480 }
481 }
482 if !contains_meaningful_info {
483 help_lines.pop();
485 }
486 } else {
487 for call_stack in group {
488 if let Some(r) = call_stack.deepest.get_rule() {
492 let helper_message = rule_to_message(r);
493 if let Some(helper_message) = helper_message {
494 help_lines.push(format!("{spacing}help: {helper_message}"));
495 }
496 }
497 }
498 }
499 }
500
501 help_lines.join("\n")
502 };
503 let error = Error::new_from_pos(
504 ErrorVariant::CustomError { message },
505 Position::new_internal(input, error_position),
506 );
507 Some(error)
508 }
509
510 fn start(&self) -> (usize, usize) {
511 match self.line_col {
512 LineColLocation::Pos(line_col) => line_col,
513 LineColLocation::Span(start_line_col, _) => start_line_col,
514 }
515 }
516
517 fn spacing(&self) -> String {
518 let line = match self.line_col {
519 LineColLocation::Pos((line, _)) => line,
520 LineColLocation::Span((start_line, _), (end_line, _)) => cmp::max(start_line, end_line),
521 };
522
523 let line_str_len = format!("{}", line).len();
524
525 let mut spacing = String::new();
526 for _ in 0..line_str_len {
527 spacing.push(' ');
528 }
529
530 spacing
531 }
532
533 fn underline(&self) -> String {
534 let mut underline = String::new();
535
536 let mut start = self.start().1;
537 let end = match self.line_col {
538 LineColLocation::Span(_, (_, mut end)) => {
539 let inverted_cols = start > end;
540 if inverted_cols {
541 mem::swap(&mut start, &mut end);
542 start -= 1;
543 end += 1;
544 }
545
546 Some(end)
547 }
548 _ => None,
549 };
550 let offset = start - 1;
551 let line_chars = self.inner.line.chars();
552
553 for c in line_chars.take(offset) {
554 match c {
555 '\t' => underline.push('\t'),
556 _ => underline.push(' '),
557 }
558 }
559
560 if let Some(end) = end {
561 underline.push('^');
562 if end - start > 1 {
563 for _ in 2..(end - start) {
564 underline.push('-');
565 }
566 underline.push('^');
567 }
568 } else {
569 underline.push_str("^---")
570 }
571
572 underline
573 }
574
575 fn message(&self) -> String {
576 self.variant.message().to_string()
577 }
578
579 fn parsing_error_message<F>(positives: &[R], negatives: &[R], mut f: F) -> String
580 where
581 F: FnMut(&R) -> String,
582 {
583 match (negatives.is_empty(), positives.is_empty()) {
584 (false, false) => format!(
585 "unexpected {}; expected {}",
586 Error::enumerate(negatives, &mut f),
587 Error::enumerate(positives, &mut f)
588 ),
589 (false, true) => format!("unexpected {}", Error::enumerate(negatives, &mut f)),
590 (true, false) => format!("expected {}", Error::enumerate(positives, &mut f)),
591 (true, true) => "unknown parsing error".to_owned(),
592 }
593 }
594
595 fn enumerate<F>(rules: &[R], f: &mut F) -> String
596 where
597 F: FnMut(&R) -> String,
598 {
599 match rules.len() {
600 1 => f(&rules[0]),
601 2 => format!("{} or {}", f(&rules[0]), f(&rules[1])),
602 l => {
603 let non_separated = f(&rules[l - 1]);
604 let separated = rules
605 .iter()
606 .take(l - 1)
607 .map(f)
608 .collect::<Vec<_>>()
609 .join(", ");
610 format!("{}, or {}", separated, non_separated)
611 }
612 }
613 }
614
615 pub(crate) fn format(&self) -> String {
616 let spacing = self.spacing();
617 let path = self
618 .inner
619 .path
620 .as_ref()
621 .map(|path| format!("{}:", path))
622 .unwrap_or_default();
623
624 let pair = (self.line_col.clone(), &self.inner.continued_line);
625 if let (LineColLocation::Span(_, end), Some(ref continued_line)) = pair {
626 let has_line_gap = end.0 - self.start().0 > 1;
627 if has_line_gap {
628 format!(
629 "{s }--> {p}{ls}:{c}\n\
630 {s } |\n\
631 {ls:w$} | {line}\n\
632 {s } | ...\n\
633 {le:w$} | {continued_line}\n\
634 {s } | {underline}\n\
635 {s } |\n\
636 {s } = {message}",
637 s = spacing,
638 w = spacing.len(),
639 p = path,
640 ls = self.start().0,
641 le = end.0,
642 c = self.start().1,
643 line = self.inner.line,
644 continued_line = continued_line,
645 underline = self.underline(),
646 message = self.message()
647 )
648 } else {
649 format!(
650 "{s }--> {p}{ls}:{c}\n\
651 {s } |\n\
652 {ls:w$} | {line}\n\
653 {le:w$} | {continued_line}\n\
654 {s } | {underline}\n\
655 {s } |\n\
656 {s } = {message}",
657 s = spacing,
658 w = spacing.len(),
659 p = path,
660 ls = self.start().0,
661 le = end.0,
662 c = self.start().1,
663 line = self.inner.line,
664 continued_line = continued_line,
665 underline = self.underline(),
666 message = self.message()
667 )
668 }
669 } else {
670 format!(
671 "{s}--> {p}{l}:{c}\n\
672 {s} |\n\
673 {l} | {line}\n\
674 {s} | {underline}\n\
675 {s} |\n\
676 {s} = {message}",
677 s = spacing,
678 p = path,
679 l = self.start().0,
680 c = self.start().1,
681 line = self.inner.line,
682 underline = self.underline(),
683 message = self.message()
684 )
685 }
686 }
687
688 #[cfg(feature = "miette-error")]
689 pub fn into_miette(self) -> impl ::miette::Diagnostic {
691 miette_adapter::MietteAdapter(self)
692 }
693}
694
695impl<R: RuleType> ErrorVariant<R> {
696 pub fn message(&self) -> Cow<'_, str> {
719 match self {
720 ErrorVariant::ParsingError {
721 ref positives,
722 ref negatives,
723 } => Cow::Owned(Error::parsing_error_message(positives, negatives, |r| {
724 format!("{:?}", r)
725 })),
726 ErrorVariant::CustomError { ref message } => Cow::Borrowed(message),
727 }
728 }
729}
730
731impl<R: RuleType> fmt::Display for Error<R> {
732 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
733 write!(f, "{}", self.format())
734 }
735}
736
737impl<R: RuleType> fmt::Display for ErrorVariant<R> {
738 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
739 match self {
740 ErrorVariant::ParsingError { .. } => write!(f, "parsing error: {}", self.message()),
741 ErrorVariant::CustomError { .. } => write!(f, "{}", self.message()),
742 }
743 }
744}
745
746fn visualize_whitespace(input: &str) -> String {
747 input.to_owned().replace('\r', "␍").replace('\n', "␊")
748}
749
750#[cfg(feature = "miette-error")]
751mod miette_adapter {
752 use alloc::string::ToString;
753 use core::fmt;
754 use std::boxed::Box;
755
756 use crate::error::LineColLocation;
757
758 use super::{Error, RuleType};
759
760 use miette::{Diagnostic, LabeledSpan, SourceCode};
761
762 #[derive(Debug)]
763 pub(crate) struct MietteAdapter<R: RuleType>(pub(crate) Error<R>);
764
765 impl<R: RuleType> Diagnostic for MietteAdapter<R> {
766 fn source_code(&self) -> Option<&dyn SourceCode> {
767 Some(&self.0.inner.line)
768 }
769
770 fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan>>> {
771 let message = self.0.variant.message().to_string();
772
773 let (offset, length) = match self.0.line_col {
774 LineColLocation::Pos((_, c)) => (c - 1, 1),
775 LineColLocation::Span((_, start_c), (_, end_c)) => {
776 (start_c - 1, end_c - start_c + 1)
777 }
778 };
779
780 let span = LabeledSpan::new(Some(message), offset, length);
781
782 Some(Box::new(std::iter::once(span)))
783 }
784
785 fn help<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
786 Some(Box::new(self.0.message()))
787 }
788 }
789
790 impl<R: RuleType> fmt::Display for MietteAdapter<R> {
791 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
792 write!(f, "Failure to parse at {:?}", self.0.line_col)
793 }
794 }
795
796 impl<R> core::error::Error for MietteAdapter<R>
797 where
798 R: RuleType,
799 Self: fmt::Debug + fmt::Display,
800 {
801 }
802}
803
804#[cfg(test)]
805mod tests {
806 use super::*;
807 use alloc::vec;
808
809 #[test]
810 fn display_parsing_error_mixed() {
811 let input = "ab\ncd\nef";
812 let pos = Position::new(input, 4).unwrap();
813 let error: Error<u32> = Error::new_from_pos(
814 ErrorVariant::ParsingError {
815 positives: vec![1, 2, 3],
816 negatives: vec![4, 5, 6],
817 },
818 pos,
819 );
820
821 assert_eq!(
822 format!("{}", error),
823 [
824 " --> 2:2",
825 " |",
826 "2 | cd",
827 " | ^---",
828 " |",
829 " = unexpected 4, 5, or 6; expected 1, 2, or 3"
830 ]
831 .join("\n")
832 );
833 }
834
835 #[test]
836 fn display_parsing_error_positives() {
837 let input = "ab\ncd\nef";
838 let pos = Position::new(input, 4).unwrap();
839 let error: Error<u32> = Error::new_from_pos(
840 ErrorVariant::ParsingError {
841 positives: vec![1, 2],
842 negatives: vec![],
843 },
844 pos,
845 );
846
847 assert_eq!(
848 format!("{}", error),
849 [
850 " --> 2:2",
851 " |",
852 "2 | cd",
853 " | ^---",
854 " |",
855 " = expected 1 or 2"
856 ]
857 .join("\n")
858 );
859 }
860
861 #[test]
862 fn display_parsing_error_negatives() {
863 let input = "ab\ncd\nef";
864 let pos = Position::new(input, 4).unwrap();
865 let error: Error<u32> = Error::new_from_pos(
866 ErrorVariant::ParsingError {
867 positives: vec![],
868 negatives: vec![4, 5, 6],
869 },
870 pos,
871 );
872
873 assert_eq!(
874 format!("{}", error),
875 [
876 " --> 2:2",
877 " |",
878 "2 | cd",
879 " | ^---",
880 " |",
881 " = unexpected 4, 5, or 6"
882 ]
883 .join("\n")
884 );
885 }
886
887 #[test]
888 fn display_parsing_error_unknown() {
889 let input = "ab\ncd\nef";
890 let pos = Position::new(input, 4).unwrap();
891 let error: Error<u32> = Error::new_from_pos(
892 ErrorVariant::ParsingError {
893 positives: vec![],
894 negatives: vec![],
895 },
896 pos,
897 );
898
899 assert_eq!(
900 format!("{}", error),
901 [
902 " --> 2:2",
903 " |",
904 "2 | cd",
905 " | ^---",
906 " |",
907 " = unknown parsing error"
908 ]
909 .join("\n")
910 );
911 }
912
913 #[test]
914 fn display_custom_pos() {
915 let input = "ab\ncd\nef";
916 let pos = Position::new(input, 4).unwrap();
917 let error: Error<u32> = Error::new_from_pos(
918 ErrorVariant::CustomError {
919 message: "error: big one".to_owned(),
920 },
921 pos,
922 );
923
924 assert_eq!(
925 format!("{}", error),
926 [
927 " --> 2:2",
928 " |",
929 "2 | cd",
930 " | ^---",
931 " |",
932 " = error: big one"
933 ]
934 .join("\n")
935 );
936 }
937
938 #[test]
939 fn display_custom_span_two_lines() {
940 let input = "ab\ncd\nefgh";
941 let start = Position::new(input, 4).unwrap();
942 let end = Position::new(input, 9).unwrap();
943 let error: Error<u32> = Error::new_from_span(
944 ErrorVariant::CustomError {
945 message: "error: big one".to_owned(),
946 },
947 start.span(&end),
948 );
949
950 assert_eq!(
951 format!("{}", error),
952 [
953 " --> 2:2",
954 " |",
955 "2 | cd",
956 "3 | efgh",
957 " | ^^",
958 " |",
959 " = error: big one"
960 ]
961 .join("\n")
962 );
963 }
964
965 #[test]
966 fn display_custom_span_three_lines() {
967 let input = "ab\ncd\nefgh";
968 let start = Position::new(input, 1).unwrap();
969 let end = Position::new(input, 9).unwrap();
970 let error: Error<u32> = Error::new_from_span(
971 ErrorVariant::CustomError {
972 message: "error: big one".to_owned(),
973 },
974 start.span(&end),
975 );
976
977 assert_eq!(
978 format!("{}", error),
979 [
980 " --> 1:2",
981 " |",
982 "1 | ab",
983 " | ...",
984 "3 | efgh",
985 " | ^^",
986 " |",
987 " = error: big one"
988 ]
989 .join("\n")
990 );
991 }
992
993 #[test]
994 fn display_custom_span_two_lines_inverted_cols() {
995 let input = "abcdef\ngh";
996 let start = Position::new(input, 5).unwrap();
997 let end = Position::new(input, 8).unwrap();
998 let error: Error<u32> = Error::new_from_span(
999 ErrorVariant::CustomError {
1000 message: "error: big one".to_owned(),
1001 },
1002 start.span(&end),
1003 );
1004
1005 assert_eq!(
1006 format!("{}", error),
1007 [
1008 " --> 1:6",
1009 " |",
1010 "1 | abcdef",
1011 "2 | gh",
1012 " | ^----^",
1013 " |",
1014 " = error: big one"
1015 ]
1016 .join("\n")
1017 );
1018 }
1019
1020 #[test]
1021 fn display_custom_span_end_after_newline() {
1022 let input = "abcdef\n";
1023 let start = Position::new(input, 0).unwrap();
1024 let end = Position::new(input, 7).unwrap();
1025 assert!(start.at_start());
1026 assert!(end.at_end());
1027
1028 let error: Error<u32> = Error::new_from_span(
1029 ErrorVariant::CustomError {
1030 message: "error: big one".to_owned(),
1031 },
1032 start.span(&end),
1033 );
1034
1035 assert_eq!(
1036 format!("{}", error),
1037 [
1038 " --> 1:1",
1039 " |",
1040 "1 | abcdef␊",
1041 " | ^-----^",
1042 " |",
1043 " = error: big one"
1044 ]
1045 .join("\n")
1046 );
1047 }
1048
1049 #[test]
1050 fn display_custom_span_empty() {
1051 let input = "";
1052 let start = Position::new(input, 0).unwrap();
1053 let end = Position::new(input, 0).unwrap();
1054 assert!(start.at_start());
1055 assert!(end.at_end());
1056
1057 let error: Error<u32> = Error::new_from_span(
1058 ErrorVariant::CustomError {
1059 message: "error: empty".to_owned(),
1060 },
1061 start.span(&end),
1062 );
1063
1064 assert_eq!(
1065 format!("{}", error),
1066 [
1067 " --> 1:1",
1068 " |",
1069 "1 | ",
1070 " | ^",
1071 " |",
1072 " = error: empty"
1073 ]
1074 .join("\n")
1075 );
1076 }
1077
1078 #[test]
1079 fn mapped_parsing_error() {
1080 let input = "ab\ncd\nef";
1081 let pos = Position::new(input, 4).unwrap();
1082 let error: Error<u32> = Error::new_from_pos(
1083 ErrorVariant::ParsingError {
1084 positives: vec![1, 2, 3],
1085 negatives: vec![4, 5, 6],
1086 },
1087 pos,
1088 )
1089 .renamed_rules(|n| format!("{}", n + 1));
1090
1091 assert_eq!(
1092 format!("{}", error),
1093 [
1094 " --> 2:2",
1095 " |",
1096 "2 | cd",
1097 " | ^---",
1098 " |",
1099 " = unexpected 5, 6, or 7; expected 2, 3, or 4"
1100 ]
1101 .join("\n")
1102 );
1103 }
1104
1105 #[test]
1106 fn error_with_path() {
1107 let input = "ab\ncd\nef";
1108 let pos = Position::new(input, 4).unwrap();
1109 let error: Error<u32> = Error::new_from_pos(
1110 ErrorVariant::ParsingError {
1111 positives: vec![1, 2, 3],
1112 negatives: vec![4, 5, 6],
1113 },
1114 pos,
1115 )
1116 .with_path("file.rs");
1117
1118 assert_eq!(
1119 format!("{}", error),
1120 [
1121 " --> file.rs:2:2",
1122 " |",
1123 "2 | cd",
1124 " | ^---",
1125 " |",
1126 " = unexpected 4, 5, or 6; expected 1, 2, or 3"
1127 ]
1128 .join("\n")
1129 );
1130 }
1131
1132 #[test]
1133 fn underline_with_tabs() {
1134 let input = "a\txbc";
1135 let pos = Position::new(input, 2).unwrap();
1136 let error: Error<u32> = Error::new_from_pos(
1137 ErrorVariant::ParsingError {
1138 positives: vec![1, 2, 3],
1139 negatives: vec![4, 5, 6],
1140 },
1141 pos,
1142 )
1143 .with_path("file.rs");
1144
1145 assert_eq!(
1146 format!("{}", error),
1147 [
1148 " --> file.rs:1:3",
1149 " |",
1150 "1 | a xbc",
1151 " | ^---",
1152 " |",
1153 " = unexpected 4, 5, or 6; expected 1, 2, or 3"
1154 ]
1155 .join("\n")
1156 );
1157 }
1158
1159 #[test]
1160 fn pos_to_lcl_conversion() {
1161 let input = "input";
1162
1163 let pos = Position::new(input, 2).unwrap();
1164
1165 assert_eq!(LineColLocation::Pos(pos.line_col()), pos.into());
1166 }
1167
1168 #[test]
1169 fn span_to_lcl_conversion() {
1170 let input = "input";
1171
1172 let span = Span::new(input, 2, 4).unwrap();
1173 let (start, end) = span.split();
1174
1175 assert_eq!(
1176 LineColLocation::Span(start.line_col(), end.line_col()),
1177 span.into()
1178 );
1179 }
1180
1181 #[cfg(feature = "miette-error")]
1182 #[test]
1183 fn miette_error() {
1184 let input = "abc\ndef";
1185 let pos = Position::new(input, 4).unwrap();
1186 let error: Error<u32> = Error::new_from_pos(
1187 ErrorVariant::ParsingError {
1188 positives: vec![1, 2, 3],
1189 negatives: vec![4, 5, 6],
1190 },
1191 pos,
1192 );
1193
1194 let miette_error = miette::Error::new(error.into_miette());
1195
1196 assert_eq!(
1197 format!("{:?}", miette_error),
1198 [
1199 " \u{1b}[31m×\u{1b}[0m Failure to parse at Pos((2, 1))",
1200 " ╭────",
1201 " \u{1b}[2m1\u{1b}[0m │ def",
1202 " · \u{1b}[35;1m┬\u{1b}[0m",
1203 " · \u{1b}[35;1m╰── \u{1b}[35;1munexpected 4, 5, or 6; expected 1, 2, or 3\u{1b}[0m\u{1b}[0m",
1204 " ╰────",
1205 "\u{1b}[36m help: \u{1b}[0munexpected 4, 5, or 6; expected 1, 2, or 3\n"
1206 ]
1207 .join("\n")
1208 );
1209 }
1210}