1use alloc::borrow::Cow;
13use alloc::borrow::ToOwned;
14use alloc::format;
15use alloc::string::String;
16use alloc::string::ToString;
17use alloc::vec::Vec;
18use core::cmp;
19use core::fmt;
20use core::mem;
21
22use crate::position::Position;
23use crate::span::Span;
24use crate::RuleType;
25
26#[derive(Clone, Debug, Eq, Hash, PartialEq)]
28#[cfg_attr(feature = "std", derive(thiserror::Error))]
29pub struct Error<R> {
30 pub variant: ErrorVariant<R>,
32 pub location: InputLocation,
34 pub line_col: LineColLocation,
36 path: Option<String>,
37 line: String,
38 continued_line: Option<String>,
39}
40
41#[derive(Clone, Debug, Eq, Hash, PartialEq)]
43#[cfg_attr(feature = "std", derive(thiserror::Error))]
44pub enum ErrorVariant<R> {
45 ParsingError {
47 positives: Vec<R>,
49 negatives: Vec<R>,
51 },
52 CustomError {
54 message: String,
56 },
57}
58
59#[derive(Clone, Debug, Eq, Hash, PartialEq)]
61pub enum InputLocation {
62 Pos(usize),
64 Span((usize, usize)),
66}
67
68#[derive(Clone, Debug, Eq, Hash, PartialEq)]
70pub enum LineColLocation {
71 Pos((usize, usize)),
73 Span((usize, usize), (usize, usize)),
75}
76
77impl From<Position<'_>> for LineColLocation {
78 fn from(value: Position<'_>) -> Self {
79 Self::Pos(value.line_col())
80 }
81}
82
83impl From<Span<'_>> for LineColLocation {
84 fn from(value: Span<'_>) -> Self {
85 let (start, end) = value.split();
86 Self::Span(start.line_col(), end.line_col())
87 }
88}
89
90impl<R: RuleType> Error<R> {
91 pub fn new_from_pos(variant: ErrorVariant<R>, pos: Position<'_>) -> Error<R> {
118 let visualize_ws = pos.match_char('\n') || pos.match_char('\r');
119 let line_of = pos.line_of();
120 let line = if visualize_ws {
121 visualize_whitespace(line_of)
122 } else {
123 line_of.replace(&['\r', '\n'][..], "")
124 };
125 Error {
126 variant,
127 location: InputLocation::Pos(pos.pos()),
128 path: None,
129 line,
130 continued_line: None,
131 line_col: LineColLocation::Pos(pos.line_col()),
132 }
133 }
134
135 pub fn new_from_span(variant: ErrorVariant<R>, span: Span<'_>) -> Error<R> {
164 let end = span.end_pos();
165 let mut end_line_col = end.line_col();
166 if end_line_col.1 == 1 {
168 let mut visual_end = end;
169 visual_end.skip_back(1);
170 let lc = visual_end.line_col();
171 end_line_col = (lc.0, lc.1 + 1);
172 };
173
174 let mut line_iter = span.lines();
175 let sl = line_iter.next().unwrap_or("");
176 let mut chars = span.as_str().chars();
177 let visualize_ws = matches!(chars.next(), Some('\n') | Some('\r'))
178 || matches!(chars.last(), Some('\n') | Some('\r'));
179 let start_line = if visualize_ws {
180 visualize_whitespace(sl)
181 } else {
182 sl.to_owned().replace(&['\r', '\n'][..], "")
183 };
184 let ll = line_iter.last();
185 let continued_line = if visualize_ws {
186 ll.map(str::to_owned)
187 } else {
188 ll.map(visualize_whitespace)
189 };
190
191 Error {
192 variant,
193 location: InputLocation::Span((span.start(), end.pos())),
194 path: None,
195 line: start_line,
196 continued_line,
197 line_col: LineColLocation::Span(span.start_pos().line_col(), end_line_col),
198 }
199 }
200
201 pub fn with_path(mut self, path: &str) -> Error<R> {
226 self.path = Some(path.to_owned());
227
228 self
229 }
230
231 pub fn path(&self) -> Option<&str> {
257 self.path.as_deref()
258 }
259
260 pub fn line(&self) -> &str {
262 self.line.as_str()
263 }
264
265 pub fn renamed_rules<F>(mut self, f: F) -> Error<R>
301 where
302 F: FnMut(&R) -> String,
303 {
304 let variant = match self.variant {
305 ErrorVariant::ParsingError {
306 positives,
307 negatives,
308 } => {
309 let message = Error::parsing_error_message(&positives, &negatives, f);
310 ErrorVariant::CustomError { message }
311 }
312 variant => variant,
313 };
314
315 self.variant = variant;
316
317 self
318 }
319
320 fn start(&self) -> (usize, usize) {
321 match self.line_col {
322 LineColLocation::Pos(line_col) => line_col,
323 LineColLocation::Span(start_line_col, _) => start_line_col,
324 }
325 }
326
327 fn spacing(&self) -> String {
328 let line = match self.line_col {
329 LineColLocation::Pos((line, _)) => line,
330 LineColLocation::Span((start_line, _), (end_line, _)) => cmp::max(start_line, end_line),
331 };
332
333 let line_str_len = format!("{}", line).len();
334
335 let mut spacing = String::new();
336 for _ in 0..line_str_len {
337 spacing.push(' ');
338 }
339
340 spacing
341 }
342
343 fn underline(&self) -> String {
344 let mut underline = String::new();
345
346 let mut start = self.start().1;
347 let end = match self.line_col {
348 LineColLocation::Span(_, (_, mut end)) => {
349 let inverted_cols = start > end;
350 if inverted_cols {
351 mem::swap(&mut start, &mut end);
352 start -= 1;
353 end += 1;
354 }
355
356 Some(end)
357 }
358 _ => None,
359 };
360 let offset = start - 1;
361 let line_chars = self.line.chars();
362
363 for c in line_chars.take(offset) {
364 match c {
365 '\t' => underline.push('\t'),
366 _ => underline.push(' '),
367 }
368 }
369
370 if let Some(end) = end {
371 underline.push('^');
372 if end - start > 1 {
373 for _ in 2..(end - start) {
374 underline.push('-');
375 }
376 underline.push('^');
377 }
378 } else {
379 underline.push_str("^---")
380 }
381
382 underline
383 }
384
385 fn message(&self) -> String {
386 self.variant.message().to_string()
387 }
388
389 fn parsing_error_message<F>(positives: &[R], negatives: &[R], mut f: F) -> String
390 where
391 F: FnMut(&R) -> String,
392 {
393 match (negatives.is_empty(), positives.is_empty()) {
394 (false, false) => format!(
395 "unexpected {}; expected {}",
396 Error::enumerate(negatives, &mut f),
397 Error::enumerate(positives, &mut f)
398 ),
399 (false, true) => format!("unexpected {}", Error::enumerate(negatives, &mut f)),
400 (true, false) => format!("expected {}", Error::enumerate(positives, &mut f)),
401 (true, true) => "unknown parsing error".to_owned(),
402 }
403 }
404
405 fn enumerate<F>(rules: &[R], f: &mut F) -> String
406 where
407 F: FnMut(&R) -> String,
408 {
409 match rules.len() {
410 1 => f(&rules[0]),
411 2 => format!("{} or {}", f(&rules[0]), f(&rules[1])),
412 l => {
413 let non_separated = f(&rules[l - 1]);
414 let separated = rules
415 .iter()
416 .take(l - 1)
417 .map(f)
418 .collect::<Vec<_>>()
419 .join(", ");
420 format!("{}, or {}", separated, non_separated)
421 }
422 }
423 }
424
425 pub(crate) fn format(&self) -> String {
426 let spacing = self.spacing();
427 let path = self
428 .path
429 .as_ref()
430 .map(|path| format!("{}:", path))
431 .unwrap_or_default();
432
433 let pair = (self.line_col.clone(), &self.continued_line);
434 if let (LineColLocation::Span(_, end), Some(ref continued_line)) = pair {
435 let has_line_gap = end.0 - self.start().0 > 1;
436 if has_line_gap {
437 format!(
438 "{s }--> {p}{ls}:{c}\n\
439 {s } |\n\
440 {ls:w$} | {line}\n\
441 {s } | ...\n\
442 {le:w$} | {continued_line}\n\
443 {s } | {underline}\n\
444 {s } |\n\
445 {s } = {message}",
446 s = spacing,
447 w = spacing.len(),
448 p = path,
449 ls = self.start().0,
450 le = end.0,
451 c = self.start().1,
452 line = self.line,
453 continued_line = continued_line,
454 underline = self.underline(),
455 message = self.message()
456 )
457 } else {
458 format!(
459 "{s }--> {p}{ls}:{c}\n\
460 {s } |\n\
461 {ls:w$} | {line}\n\
462 {le:w$} | {continued_line}\n\
463 {s } | {underline}\n\
464 {s } |\n\
465 {s } = {message}",
466 s = spacing,
467 w = spacing.len(),
468 p = path,
469 ls = self.start().0,
470 le = end.0,
471 c = self.start().1,
472 line = self.line,
473 continued_line = continued_line,
474 underline = self.underline(),
475 message = self.message()
476 )
477 }
478 } else {
479 format!(
480 "{s}--> {p}{l}:{c}\n\
481 {s} |\n\
482 {l} | {line}\n\
483 {s} | {underline}\n\
484 {s} |\n\
485 {s} = {message}",
486 s = spacing,
487 p = path,
488 l = self.start().0,
489 c = self.start().1,
490 line = self.line,
491 underline = self.underline(),
492 message = self.message()
493 )
494 }
495 }
496}
497
498impl<R: RuleType> ErrorVariant<R> {
499 pub fn message(&self) -> Cow<'_, str> {
522 match self {
523 ErrorVariant::ParsingError {
524 ref positives,
525 ref negatives,
526 } => Cow::Owned(Error::parsing_error_message(positives, negatives, |r| {
527 format!("{:?}", r)
528 })),
529 ErrorVariant::CustomError { ref message } => Cow::Borrowed(message),
530 }
531 }
532}
533
534impl<R: RuleType> fmt::Display for Error<R> {
535 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
536 write!(f, "{}", self.format())
537 }
538}
539
540impl<R: RuleType> fmt::Display for ErrorVariant<R> {
541 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
542 match self {
543 ErrorVariant::ParsingError { .. } => write!(f, "parsing error: {}", self.message()),
544 ErrorVariant::CustomError { .. } => write!(f, "{}", self.message()),
545 }
546 }
547}
548
549fn visualize_whitespace(input: &str) -> String {
550 input.to_owned().replace('\r', "␍").replace('\n', "␊")
551}
552
553#[cfg(test)]
554mod tests {
555 use super::super::position;
556 use super::*;
557 use alloc::vec;
558
559 #[test]
560 fn display_parsing_error_mixed() {
561 let input = "ab\ncd\nef";
562 let pos = position::Position::new(input, 4).unwrap();
563 let error: Error<u32> = Error::new_from_pos(
564 ErrorVariant::ParsingError {
565 positives: vec![1, 2, 3],
566 negatives: vec![4, 5, 6],
567 },
568 pos,
569 );
570
571 assert_eq!(
572 format!("{}", error),
573 [
574 " --> 2:2",
575 " |",
576 "2 | cd",
577 " | ^---",
578 " |",
579 " = unexpected 4, 5, or 6; expected 1, 2, or 3"
580 ]
581 .join("\n")
582 );
583 }
584
585 #[test]
586 fn display_parsing_error_positives() {
587 let input = "ab\ncd\nef";
588 let pos = position::Position::new(input, 4).unwrap();
589 let error: Error<u32> = Error::new_from_pos(
590 ErrorVariant::ParsingError {
591 positives: vec![1, 2],
592 negatives: vec![],
593 },
594 pos,
595 );
596
597 assert_eq!(
598 format!("{}", error),
599 [
600 " --> 2:2",
601 " |",
602 "2 | cd",
603 " | ^---",
604 " |",
605 " = expected 1 or 2"
606 ]
607 .join("\n")
608 );
609 }
610
611 #[test]
612 fn display_parsing_error_negatives() {
613 let input = "ab\ncd\nef";
614 let pos = position::Position::new(input, 4).unwrap();
615 let error: Error<u32> = Error::new_from_pos(
616 ErrorVariant::ParsingError {
617 positives: vec![],
618 negatives: vec![4, 5, 6],
619 },
620 pos,
621 );
622
623 assert_eq!(
624 format!("{}", error),
625 [
626 " --> 2:2",
627 " |",
628 "2 | cd",
629 " | ^---",
630 " |",
631 " = unexpected 4, 5, or 6"
632 ]
633 .join("\n")
634 );
635 }
636
637 #[test]
638 fn display_parsing_error_unknown() {
639 let input = "ab\ncd\nef";
640 let pos = position::Position::new(input, 4).unwrap();
641 let error: Error<u32> = Error::new_from_pos(
642 ErrorVariant::ParsingError {
643 positives: vec![],
644 negatives: vec![],
645 },
646 pos,
647 );
648
649 assert_eq!(
650 format!("{}", error),
651 [
652 " --> 2:2",
653 " |",
654 "2 | cd",
655 " | ^---",
656 " |",
657 " = unknown parsing error"
658 ]
659 .join("\n")
660 );
661 }
662
663 #[test]
664 fn display_custom_pos() {
665 let input = "ab\ncd\nef";
666 let pos = position::Position::new(input, 4).unwrap();
667 let error: Error<u32> = Error::new_from_pos(
668 ErrorVariant::CustomError {
669 message: "error: big one".to_owned(),
670 },
671 pos,
672 );
673
674 assert_eq!(
675 format!("{}", error),
676 [
677 " --> 2:2",
678 " |",
679 "2 | cd",
680 " | ^---",
681 " |",
682 " = error: big one"
683 ]
684 .join("\n")
685 );
686 }
687
688 #[test]
689 fn display_custom_span_two_lines() {
690 let input = "ab\ncd\nefgh";
691 let start = position::Position::new(input, 4).unwrap();
692 let end = position::Position::new(input, 9).unwrap();
693 let error: Error<u32> = Error::new_from_span(
694 ErrorVariant::CustomError {
695 message: "error: big one".to_owned(),
696 },
697 start.span(&end),
698 );
699
700 assert_eq!(
701 format!("{}", error),
702 [
703 " --> 2:2",
704 " |",
705 "2 | cd",
706 "3 | efgh",
707 " | ^^",
708 " |",
709 " = error: big one"
710 ]
711 .join("\n")
712 );
713 }
714
715 #[test]
716 fn display_custom_span_three_lines() {
717 let input = "ab\ncd\nefgh";
718 let start = position::Position::new(input, 1).unwrap();
719 let end = position::Position::new(input, 9).unwrap();
720 let error: Error<u32> = Error::new_from_span(
721 ErrorVariant::CustomError {
722 message: "error: big one".to_owned(),
723 },
724 start.span(&end),
725 );
726
727 assert_eq!(
728 format!("{}", error),
729 [
730 " --> 1:2",
731 " |",
732 "1 | ab",
733 " | ...",
734 "3 | efgh",
735 " | ^^",
736 " |",
737 " = error: big one"
738 ]
739 .join("\n")
740 );
741 }
742
743 #[test]
744 fn display_custom_span_two_lines_inverted_cols() {
745 let input = "abcdef\ngh";
746 let start = position::Position::new(input, 5).unwrap();
747 let end = position::Position::new(input, 8).unwrap();
748 let error: Error<u32> = Error::new_from_span(
749 ErrorVariant::CustomError {
750 message: "error: big one".to_owned(),
751 },
752 start.span(&end),
753 );
754
755 assert_eq!(
756 format!("{}", error),
757 [
758 " --> 1:6",
759 " |",
760 "1 | abcdef",
761 "2 | gh",
762 " | ^----^",
763 " |",
764 " = error: big one"
765 ]
766 .join("\n")
767 );
768 }
769
770 #[test]
771 fn display_custom_span_end_after_newline() {
772 let input = "abcdef\n";
773 let start = position::Position::new(input, 0).unwrap();
774 let end = position::Position::new(input, 7).unwrap();
775 assert!(start.at_start());
776 assert!(end.at_end());
777
778 let error: Error<u32> = Error::new_from_span(
779 ErrorVariant::CustomError {
780 message: "error: big one".to_owned(),
781 },
782 start.span(&end),
783 );
784
785 assert_eq!(
786 format!("{}", error),
787 [
788 " --> 1:1",
789 " |",
790 "1 | abcdef␊",
791 " | ^-----^",
792 " |",
793 " = error: big one"
794 ]
795 .join("\n")
796 );
797 }
798
799 #[test]
800 fn display_custom_span_empty() {
801 let input = "";
802 let start = position::Position::new(input, 0).unwrap();
803 let end = position::Position::new(input, 0).unwrap();
804 assert!(start.at_start());
805 assert!(end.at_end());
806
807 let error: Error<u32> = Error::new_from_span(
808 ErrorVariant::CustomError {
809 message: "error: empty".to_owned(),
810 },
811 start.span(&end),
812 );
813
814 assert_eq!(
815 format!("{}", error),
816 [
817 " --> 1:1",
818 " |",
819 "1 | ",
820 " | ^",
821 " |",
822 " = error: empty"
823 ]
824 .join("\n")
825 );
826 }
827
828 #[test]
829 fn mapped_parsing_error() {
830 let input = "ab\ncd\nef";
831 let pos = position::Position::new(input, 4).unwrap();
832 let error: Error<u32> = Error::new_from_pos(
833 ErrorVariant::ParsingError {
834 positives: vec![1, 2, 3],
835 negatives: vec![4, 5, 6],
836 },
837 pos,
838 )
839 .renamed_rules(|n| format!("{}", n + 1));
840
841 assert_eq!(
842 format!("{}", error),
843 [
844 " --> 2:2",
845 " |",
846 "2 | cd",
847 " | ^---",
848 " |",
849 " = unexpected 5, 6, or 7; expected 2, 3, or 4"
850 ]
851 .join("\n")
852 );
853 }
854
855 #[test]
856 fn error_with_path() {
857 let input = "ab\ncd\nef";
858 let pos = position::Position::new(input, 4).unwrap();
859 let error: Error<u32> = Error::new_from_pos(
860 ErrorVariant::ParsingError {
861 positives: vec![1, 2, 3],
862 negatives: vec![4, 5, 6],
863 },
864 pos,
865 )
866 .with_path("file.rs");
867
868 assert_eq!(
869 format!("{}", error),
870 [
871 " --> file.rs:2:2",
872 " |",
873 "2 | cd",
874 " | ^---",
875 " |",
876 " = unexpected 4, 5, or 6; expected 1, 2, or 3"
877 ]
878 .join("\n")
879 );
880 }
881
882 #[test]
883 fn underline_with_tabs() {
884 let input = "a\txbc";
885 let pos = position::Position::new(input, 2).unwrap();
886 let error: Error<u32> = Error::new_from_pos(
887 ErrorVariant::ParsingError {
888 positives: vec![1, 2, 3],
889 negatives: vec![4, 5, 6],
890 },
891 pos,
892 )
893 .with_path("file.rs");
894
895 assert_eq!(
896 format!("{}", error),
897 [
898 " --> file.rs:1:3",
899 " |",
900 "1 | a xbc",
901 " | ^---",
902 " |",
903 " = unexpected 4, 5, or 6; expected 1, 2, or 3"
904 ]
905 .join("\n")
906 );
907 }
908
909 #[test]
910 fn pos_to_lcl_conversion() {
911 let input = "input";
912
913 let pos = Position::new(input, 2).unwrap();
914
915 assert_eq!(LineColLocation::Pos(pos.line_col()), pos.into());
916 }
917
918 #[test]
919 fn span_to_lcl_conversion() {
920 let input = "input";
921
922 let span = Span::new(input, 2, 4).unwrap();
923 let (start, end) = span.split();
924
925 assert_eq!(
926 LineColLocation::Span(start.line_col(), end.line_col()),
927 span.into()
928 );
929 }
930}