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