1use alloc::borrow::Cow;
13use alloc::borrow::ToOwned;
14use alloc::format;
15use alloc::string::String;
16use alloc::string::ToString;
17use alloc::vec::Vec;
18use std::cmp;
19use std::fmt;
20use std::mem;
21#[cfg(test)]
22use std::sync::Arc;
23
24use position::Position;
25use span::Span;
26use RuleType;
27
28#[derive(Clone, Debug, Eq, Hash, PartialEq)]
30pub struct Error<R> {
31 pub variant: ErrorVariant<R>,
33 pub location: InputLocation,
35 pub line_col: LineColLocation,
37 path: Option<String>,
38 line: String,
39 continued_line: Option<String>,
40}
41
42#[derive(Clone, Debug, Eq, Hash, PartialEq)]
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<R: RuleType> Error<R> {
78 #[allow(clippy::needless_pass_by_value)]
106 pub fn new_from_pos(variant: ErrorVariant<R>, pos: Position) -> Error<R> {
107 Error {
108 variant,
109 location: InputLocation::Pos(pos.pos()),
110 path: None,
111 line: visualize_whitespace(pos.line_of()),
112 continued_line: None,
113 line_col: LineColLocation::Pos(pos.line_col()),
114 }
115 }
116
117 #[allow(clippy::needless_pass_by_value)]
147 pub fn new_from_span(variant: ErrorVariant<R>, span: Span) -> Error<R> {
148 let end = span.end_pos();
149
150 let mut end_line_col = end.line_col();
151 if end_line_col.1 == 1 {
153 let mut visual_end = end.clone();
154 visual_end.skip_back(1);
155 let lc = visual_end.line_col();
156 end_line_col = (lc.0, lc.1 + 1);
157 };
158
159 let mut line_iter = span.lines();
160 let start_line = visualize_whitespace(line_iter.next().unwrap_or(""));
161 let continued_line = line_iter.last().map(visualize_whitespace);
162
163 Error {
164 variant,
165 location: InputLocation::Span((span.start(), end.pos())),
166 path: None,
167 line: start_line,
168 continued_line,
169 line_col: LineColLocation::Span(span.start_pos().line_col(), end_line_col),
170 }
171 }
172
173 pub fn with_path(mut self, path: &str) -> Error<R> {
199 self.path = Some(path.to_owned());
200
201 self
202 }
203
204 pub fn path(&self) -> Option<&str> {
231 self.path.as_deref()
232 }
233
234 pub fn renamed_rules<F>(mut self, f: F) -> Error<R>
271 where
272 F: FnMut(&R) -> String,
273 {
274 let variant = match self.variant {
275 ErrorVariant::ParsingError {
276 positives,
277 negatives,
278 } => {
279 let message = Error::parsing_error_message(&positives, &negatives, f);
280 ErrorVariant::CustomError { message }
281 }
282 variant => variant,
283 };
284
285 self.variant = variant;
286
287 self
288 }
289
290 fn start(&self) -> (usize, usize) {
291 match self.line_col {
292 LineColLocation::Pos(line_col) => line_col,
293 LineColLocation::Span(start_line_col, _) => start_line_col,
294 }
295 }
296
297 fn spacing(&self) -> String {
298 let line = match self.line_col {
299 LineColLocation::Pos((line, _)) => line,
300 LineColLocation::Span((start_line, _), (end_line, _)) => cmp::max(start_line, end_line),
301 };
302
303 let line_str_len = format!("{}", line).len();
304
305 let mut spacing = String::new();
306 for _ in 0..line_str_len {
307 spacing.push(' ');
308 }
309
310 spacing
311 }
312
313 fn underline(&self) -> String {
314 let mut underline = String::new();
315
316 let mut start = self.start().1;
317 let end = match self.line_col {
318 LineColLocation::Span(_, (_, mut end)) => {
319 let inverted_cols = start > end;
320 if inverted_cols {
321 mem::swap(&mut start, &mut end);
322 start -= 1;
323 end += 1;
324 }
325
326 Some(end)
327 }
328 _ => None,
329 };
330 let offset = start - 1;
331 let line_chars = self.line.chars();
332
333 for c in line_chars.take(offset) {
334 match c {
335 '\t' => underline.push('\t'),
336 _ => underline.push(' '),
337 }
338 }
339
340 if let Some(end) = end {
341 if end - start > 1 {
342 underline.push('^');
343 for _ in 2..(end - start) {
344 underline.push('-');
345 }
346 underline.push('^');
347 } else {
348 underline.push('^');
349 }
350 } else {
351 underline.push_str("^---")
352 }
353
354 underline
355 }
356
357 fn message(&self) -> String {
358 self.variant.message().to_string()
359 }
360
361 fn parsing_error_message<F>(positives: &[R], negatives: &[R], mut f: F) -> String
362 where
363 F: FnMut(&R) -> String,
364 {
365 match (negatives.is_empty(), positives.is_empty()) {
366 (false, false) => format!(
367 "unexpected {}; expected {}",
368 Error::enumerate(negatives, &mut f),
369 Error::enumerate(positives, &mut f)
370 ),
371 (false, true) => format!("unexpected {}", Error::enumerate(negatives, &mut f)),
372 (true, false) => format!("expected {}", Error::enumerate(positives, &mut f)),
373 (true, true) => "unknown parsing error".to_owned(),
374 }
375 }
376
377 fn enumerate<F>(rules: &[R], f: &mut F) -> String
378 where
379 F: FnMut(&R) -> String,
380 {
381 match rules.len() {
382 1 => f(&rules[0]),
383 2 => format!("{} or {}", f(&rules[0]), f(&rules[1])),
384 l => {
385 let separated = rules
386 .iter()
387 .take(l - 1)
388 .map(|r| f(r))
389 .collect::<Vec<_>>()
390 .join(", ");
391 format!("{}, or {}", separated, f(&rules[l - 1]))
392 }
393 }
394 }
395
396 pub(crate) fn format(&self) -> String {
397 let spacing = self.spacing();
398 let path = self
399 .path
400 .as_ref()
401 .map(|path| format!("{}:", path))
402 .unwrap_or_default();
403
404 let pair = (self.line_col.clone(), &self.continued_line);
405 if let (LineColLocation::Span(_, end), &Some(ref continued_line)) = pair {
406 let has_line_gap = end.0 - self.start().0 > 1;
407 if has_line_gap {
408 format!(
409 "{s }--> {p}{ls}:{c}\n\
410 {s } |\n\
411 {ls:w$} | {line}\n\
412 {s } | ...\n\
413 {le:w$} | {continued_line}\n\
414 {s } | {underline}\n\
415 {s } |\n\
416 {s } = {message}",
417 s = spacing,
418 w = spacing.len(),
419 p = path,
420 ls = self.start().0,
421 le = end.0,
422 c = self.start().1,
423 line = self.line,
424 continued_line = continued_line,
425 underline = self.underline(),
426 message = self.message()
427 )
428 } else {
429 format!(
430 "{s }--> {p}{ls}:{c}\n\
431 {s } |\n\
432 {ls:w$} | {line}\n\
433 {le:w$} | {continued_line}\n\
434 {s } | {underline}\n\
435 {s } |\n\
436 {s } = {message}",
437 s = spacing,
438 w = spacing.len(),
439 p = path,
440 ls = self.start().0,
441 le = end.0,
442 c = self.start().1,
443 line = self.line,
444 continued_line = continued_line,
445 underline = self.underline(),
446 message = self.message()
447 )
448 }
449 } else {
450 format!(
451 "{s}--> {p}{l}:{c}\n\
452 {s} |\n\
453 {l} | {line}\n\
454 {s} | {underline}\n\
455 {s} |\n\
456 {s} = {message}",
457 s = spacing,
458 p = path,
459 l = self.start().0,
460 c = self.start().1,
461 line = self.line,
462 underline = self.underline(),
463 message = self.message()
464 )
465 }
466 }
467}
468
469impl<R: RuleType> ErrorVariant<R> {
470 pub fn message(&self) -> Cow<str> {
493 match self {
494 ErrorVariant::ParsingError {
495 ref positives,
496 ref negatives,
497 } => Cow::Owned(Error::parsing_error_message(positives, negatives, |r| {
498 format!("{:?}", r)
499 })),
500 ErrorVariant::CustomError { ref message } => Cow::Borrowed(message),
501 }
502 }
503}
504
505impl<R: RuleType> fmt::Display for Error<R> {
506 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
507 write!(f, "{}", self.format())
508 }
509}
510
511#[cfg(feature = "std")]
512impl<'i, R: RuleType> std::error::Error for Error<R> {
513 fn description(&self) -> &str {
514 match self.variant {
515 ErrorVariant::ParsingError { .. } => "parsing error",
516 ErrorVariant::CustomError { ref message } => message,
517 }
518 }
519}
520
521fn visualize_whitespace(input: &str) -> String {
522 input.to_owned().replace('\r', "␍").replace('\n', "␊")
523}
524
525#[cfg(test)]
526mod tests {
527 use super::super::position;
528 use super::*;
529 use alloc::vec;
530
531 #[test]
532 fn display_parsing_error_mixed() {
533 let input: Arc<str> = Arc::from("ab\ncd\nef");
534 let pos = position::Position::new(input, 4).unwrap();
535 let error: Error<u32> = Error::new_from_pos(
536 ErrorVariant::ParsingError {
537 positives: vec![1, 2, 3],
538 negatives: vec![4, 5, 6],
539 },
540 pos,
541 );
542
543 assert_eq!(
544 format!("{}", error),
545 vec![
546 " --> 2:2",
547 " |",
548 "2 | cd␊",
549 " | ^---",
550 " |",
551 " = unexpected 4, 5, or 6; expected 1, 2, or 3",
552 ]
553 .join("\n")
554 );
555 }
556
557 #[test]
558 fn display_parsing_error_positives() {
559 let input: Arc<str> = Arc::from("ab\ncd\nef");
560 let pos = position::Position::new(input, 4).unwrap();
561 let error: Error<u32> = Error::new_from_pos(
562 ErrorVariant::ParsingError {
563 positives: vec![1, 2],
564 negatives: vec![],
565 },
566 pos,
567 );
568
569 assert_eq!(
570 format!("{}", error),
571 vec![
572 " --> 2:2",
573 " |",
574 "2 | cd␊",
575 " | ^---",
576 " |",
577 " = expected 1 or 2",
578 ]
579 .join("\n")
580 );
581 }
582
583 #[test]
584 fn display_parsing_error_negatives() {
585 let input: Arc<str> = Arc::from("ab\ncd\nef");
586 let pos = position::Position::new(input, 4).unwrap();
587 let error: Error<u32> = Error::new_from_pos(
588 ErrorVariant::ParsingError {
589 positives: vec![],
590 negatives: vec![4, 5, 6],
591 },
592 pos,
593 );
594
595 assert_eq!(
596 format!("{}", error),
597 vec![
598 " --> 2:2",
599 " |",
600 "2 | cd␊",
601 " | ^---",
602 " |",
603 " = unexpected 4, 5, or 6",
604 ]
605 .join("\n")
606 );
607 }
608
609 #[test]
610 fn display_parsing_error_unknown() {
611 let input: Arc<str> = Arc::from("ab\ncd\nef");
612 let pos = position::Position::new(input, 4).unwrap();
613 let error: Error<u32> = Error::new_from_pos(
614 ErrorVariant::ParsingError {
615 positives: vec![],
616 negatives: vec![],
617 },
618 pos,
619 );
620
621 assert_eq!(
622 format!("{}", error),
623 vec![
624 " --> 2:2",
625 " |",
626 "2 | cd␊",
627 " | ^---",
628 " |",
629 " = unknown parsing error",
630 ]
631 .join("\n")
632 );
633 }
634
635 #[test]
636 fn display_custom_pos() {
637 let input: Arc<str> = Arc::from("ab\ncd\nef");
638 let pos = position::Position::new(input, 4).unwrap();
639 let error: Error<u32> = Error::new_from_pos(
640 ErrorVariant::CustomError {
641 message: "error: big one".to_owned(),
642 },
643 pos,
644 );
645
646 assert_eq!(
647 format!("{}", error),
648 vec![
649 " --> 2:2",
650 " |",
651 "2 | cd␊",
652 " | ^---",
653 " |",
654 " = error: big one",
655 ]
656 .join("\n")
657 );
658 }
659
660 #[test]
661 fn display_custom_span_two_lines() {
662 let input: Arc<str> = Arc::from("ab\ncd\nefgh");
663 let start = position::Position::new(input.clone(), 4).unwrap();
664 let end = position::Position::new(input.clone(), 9).unwrap();
665 let error: Error<u32> = Error::new_from_span(
666 ErrorVariant::CustomError {
667 message: "error: big one".to_owned(),
668 },
669 start.span(&end),
670 );
671
672 assert_eq!(
673 format!("{}", error),
674 vec![
675 " --> 2:2",
676 " |",
677 "2 | cd␊",
678 "3 | efgh",
679 " | ^^",
680 " |",
681 " = error: big one",
682 ]
683 .join("\n")
684 );
685 }
686
687 #[test]
688 fn display_custom_span_three_lines() {
689 let input: Arc<str> = Arc::from("ab\ncd\nefgh");
690 let start = position::Position::new(input.clone(), 1).unwrap();
691 let end = position::Position::new(input.clone(), 9).unwrap();
692 let error: Error<u32> = Error::new_from_span(
693 ErrorVariant::CustomError {
694 message: "error: big one".to_owned(),
695 },
696 start.span(&end),
697 );
698
699 assert_eq!(
700 format!("{}", error),
701 vec![
702 " --> 1:2",
703 " |",
704 "1 | ab␊",
705 " | ...",
706 "3 | efgh",
707 " | ^^",
708 " |",
709 " = error: big one",
710 ]
711 .join("\n")
712 );
713 }
714
715 #[test]
716 fn display_custom_span_two_lines_inverted_cols() {
717 let input: Arc<str> = Arc::from("abcdef\ngh");
718 let start = position::Position::new(input.clone(), 5).unwrap();
719 let end = position::Position::new(input.clone(), 8).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 vec![
730 " --> 1:6",
731 " |",
732 "1 | abcdef␊",
733 "2 | gh",
734 " | ^----^",
735 " |",
736 " = error: big one",
737 ]
738 .join("\n")
739 );
740 }
741
742 #[test]
743 fn display_custom_span_end_after_newline() {
744 let input: Arc<str> = Arc::from("abcdef\n");
745 let start = position::Position::new(input.clone(), 0).unwrap();
746 let end = position::Position::new(input.clone(), 7).unwrap();
747 assert!(start.at_start());
748 assert!(end.at_end());
749
750 let error: Error<u32> = Error::new_from_span(
751 ErrorVariant::CustomError {
752 message: "error: big one".to_owned(),
753 },
754 start.span(&end),
755 );
756
757 assert_eq!(
758 format!("{}", error),
759 vec![
760 " --> 1:1",
761 " |",
762 "1 | abcdef␊",
763 " | ^-----^",
764 " |",
765 " = error: big one",
766 ]
767 .join("\n")
768 );
769 }
770
771 #[test]
772 fn display_custom_span_empty() {
773 let input: Arc<str> = Arc::from("");
774 let start = position::Position::new(input.clone(), 0).unwrap();
775 let end = position::Position::new(input.clone(), 0).unwrap();
776 assert!(start.at_start());
777 assert!(end.at_end());
778
779 let error: Error<u32> = Error::new_from_span(
780 ErrorVariant::CustomError {
781 message: "error: empty".to_owned(),
782 },
783 start.span(&end),
784 );
785
786 assert_eq!(
787 format!("{}", error),
788 vec![
789 " --> 1:1",
790 " |",
791 "1 | ",
792 " | ^",
793 " |",
794 " = error: empty",
795 ]
796 .join("\n")
797 );
798 }
799
800 #[test]
801 fn mapped_parsing_error() {
802 let input: Arc<str> = Arc::from("ab\ncd\nef");
803 let pos = position::Position::new(input, 4).unwrap();
804 let error: Error<u32> = Error::new_from_pos(
805 ErrorVariant::ParsingError {
806 positives: vec![1, 2, 3],
807 negatives: vec![4, 5, 6],
808 },
809 pos,
810 )
811 .renamed_rules(|n| format!("{}", n + 1));
812
813 assert_eq!(
814 format!("{}", error),
815 vec![
816 " --> 2:2",
817 " |",
818 "2 | cd␊",
819 " | ^---",
820 " |",
821 " = unexpected 5, 6, or 7; expected 2, 3, or 4",
822 ]
823 .join("\n")
824 );
825 }
826
827 #[test]
828 fn error_with_path() {
829 let input: Arc<str> = Arc::from("ab\ncd\nef");
830 let pos = position::Position::new(input, 4).unwrap();
831 let error: Error<u32> = Error::new_from_pos(
832 ErrorVariant::ParsingError {
833 positives: vec![1, 2, 3],
834 negatives: vec![4, 5, 6],
835 },
836 pos,
837 )
838 .with_path("file.rs");
839
840 assert_eq!(
841 format!("{}", error),
842 vec![
843 " --> file.rs:2:2",
844 " |",
845 "2 | cd␊",
846 " | ^---",
847 " |",
848 " = unexpected 4, 5, or 6; expected 1, 2, or 3",
849 ]
850 .join("\n")
851 );
852 }
853
854 #[test]
855 fn underline_with_tabs() {
856 let input: Arc<str> = Arc::from("a\txbc");
857 let pos = position::Position::new(input, 2).unwrap();
858 let error: Error<u32> = Error::new_from_pos(
859 ErrorVariant::ParsingError {
860 positives: vec![1, 2, 3],
861 negatives: vec![4, 5, 6],
862 },
863 pos,
864 )
865 .with_path("file.rs");
866
867 assert_eq!(
868 format!("{}", error),
869 vec![
870 " --> file.rs:1:3",
871 " |",
872 "1 | a xbc",
873 " | ^---",
874 " |",
875 " = unexpected 4, 5, or 6; expected 1, 2, or 3",
876 ]
877 .join("\n")
878 );
879 }
880}