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