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
676impl<R: RuleType> ErrorVariant<R> {
677 pub fn message(&self) -> Cow<'_, str> {
700 match self {
701 ErrorVariant::ParsingError {
702 ref positives,
703 ref negatives,
704 } => Cow::Owned(Error::parsing_error_message(positives, negatives, |r| {
705 format!("{:?}", r)
706 })),
707 ErrorVariant::CustomError { ref message } => Cow::Borrowed(message),
708 }
709 }
710}
711
712impl<R: RuleType> fmt::Display for Error<R> {
713 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
714 write!(f, "{}", self.format())
715 }
716}
717
718impl<R: RuleType> fmt::Display for ErrorVariant<R> {
719 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
720 match self {
721 ErrorVariant::ParsingError { .. } => write!(f, "parsing error: {}", self.message()),
722 ErrorVariant::CustomError { .. } => write!(f, "{}", self.message()),
723 }
724 }
725}
726
727fn visualize_whitespace(input: &str) -> String {
728 input.to_owned().replace('\r', "␍").replace('\n', "␊")
729}
730
731#[cfg(test)]
732mod tests {
733 use super::super::position;
734 use super::*;
735 use alloc::vec;
736
737 #[test]
738 fn display_parsing_error_mixed() {
739 let input = "ab\ncd\nef";
740 let pos = position::Position::new(input, 4).unwrap();
741 let error: Error<u32> = Error::new_from_pos(
742 ErrorVariant::ParsingError {
743 positives: vec![1, 2, 3],
744 negatives: vec![4, 5, 6],
745 },
746 pos,
747 );
748
749 assert_eq!(
750 format!("{}", error),
751 [
752 " --> 2:2",
753 " |",
754 "2 | cd",
755 " | ^---",
756 " |",
757 " = unexpected 4, 5, or 6; expected 1, 2, or 3"
758 ]
759 .join("\n")
760 );
761 }
762
763 #[test]
764 fn display_parsing_error_positives() {
765 let input = "ab\ncd\nef";
766 let pos = position::Position::new(input, 4).unwrap();
767 let error: Error<u32> = Error::new_from_pos(
768 ErrorVariant::ParsingError {
769 positives: vec![1, 2],
770 negatives: vec![],
771 },
772 pos,
773 );
774
775 assert_eq!(
776 format!("{}", error),
777 [
778 " --> 2:2",
779 " |",
780 "2 | cd",
781 " | ^---",
782 " |",
783 " = expected 1 or 2"
784 ]
785 .join("\n")
786 );
787 }
788
789 #[test]
790 fn display_parsing_error_negatives() {
791 let input = "ab\ncd\nef";
792 let pos = position::Position::new(input, 4).unwrap();
793 let error: Error<u32> = Error::new_from_pos(
794 ErrorVariant::ParsingError {
795 positives: vec![],
796 negatives: vec![4, 5, 6],
797 },
798 pos,
799 );
800
801 assert_eq!(
802 format!("{}", error),
803 [
804 " --> 2:2",
805 " |",
806 "2 | cd",
807 " | ^---",
808 " |",
809 " = unexpected 4, 5, or 6"
810 ]
811 .join("\n")
812 );
813 }
814
815 #[test]
816 fn display_parsing_error_unknown() {
817 let input = "ab\ncd\nef";
818 let pos = position::Position::new(input, 4).unwrap();
819 let error: Error<u32> = Error::new_from_pos(
820 ErrorVariant::ParsingError {
821 positives: vec![],
822 negatives: vec![],
823 },
824 pos,
825 );
826
827 assert_eq!(
828 format!("{}", error),
829 [
830 " --> 2:2",
831 " |",
832 "2 | cd",
833 " | ^---",
834 " |",
835 " = unknown parsing error"
836 ]
837 .join("\n")
838 );
839 }
840
841 #[test]
842 fn display_custom_pos() {
843 let input = "ab\ncd\nef";
844 let pos = position::Position::new(input, 4).unwrap();
845 let error: Error<u32> = Error::new_from_pos(
846 ErrorVariant::CustomError {
847 message: "error: big one".to_owned(),
848 },
849 pos,
850 );
851
852 assert_eq!(
853 format!("{}", error),
854 [
855 " --> 2:2",
856 " |",
857 "2 | cd",
858 " | ^---",
859 " |",
860 " = error: big one"
861 ]
862 .join("\n")
863 );
864 }
865
866 #[test]
867 fn display_custom_span_two_lines() {
868 let input = "ab\ncd\nefgh";
869 let start = position::Position::new(input, 4).unwrap();
870 let end = position::Position::new(input, 9).unwrap();
871 let error: Error<u32> = Error::new_from_span(
872 ErrorVariant::CustomError {
873 message: "error: big one".to_owned(),
874 },
875 start.span(&end),
876 );
877
878 assert_eq!(
879 format!("{}", error),
880 [
881 " --> 2:2",
882 " |",
883 "2 | cd",
884 "3 | efgh",
885 " | ^^",
886 " |",
887 " = error: big one"
888 ]
889 .join("\n")
890 );
891 }
892
893 #[test]
894 fn display_custom_span_three_lines() {
895 let input = "ab\ncd\nefgh";
896 let start = position::Position::new(input, 1).unwrap();
897 let end = position::Position::new(input, 9).unwrap();
898 let error: Error<u32> = Error::new_from_span(
899 ErrorVariant::CustomError {
900 message: "error: big one".to_owned(),
901 },
902 start.span(&end),
903 );
904
905 assert_eq!(
906 format!("{}", error),
907 [
908 " --> 1:2",
909 " |",
910 "1 | ab",
911 " | ...",
912 "3 | efgh",
913 " | ^^",
914 " |",
915 " = error: big one"
916 ]
917 .join("\n")
918 );
919 }
920
921 #[test]
922 fn display_custom_span_two_lines_inverted_cols() {
923 let input = "abcdef\ngh";
924 let start = position::Position::new(input, 5).unwrap();
925 let end = position::Position::new(input, 8).unwrap();
926 let error: Error<u32> = Error::new_from_span(
927 ErrorVariant::CustomError {
928 message: "error: big one".to_owned(),
929 },
930 start.span(&end),
931 );
932
933 assert_eq!(
934 format!("{}", error),
935 [
936 " --> 1:6",
937 " |",
938 "1 | abcdef",
939 "2 | gh",
940 " | ^----^",
941 " |",
942 " = error: big one"
943 ]
944 .join("\n")
945 );
946 }
947
948 #[test]
949 fn display_custom_span_end_after_newline() {
950 let input = "abcdef\n";
951 let start = position::Position::new(input, 0).unwrap();
952 let end = position::Position::new(input, 7).unwrap();
953 assert!(start.at_start());
954 assert!(end.at_end());
955
956 let error: Error<u32> = Error::new_from_span(
957 ErrorVariant::CustomError {
958 message: "error: big one".to_owned(),
959 },
960 start.span(&end),
961 );
962
963 assert_eq!(
964 format!("{}", error),
965 [
966 " --> 1:1",
967 " |",
968 "1 | abcdef␊",
969 " | ^-----^",
970 " |",
971 " = error: big one"
972 ]
973 .join("\n")
974 );
975 }
976
977 #[test]
978 fn display_custom_span_empty() {
979 let input = "";
980 let start = position::Position::new(input, 0).unwrap();
981 let end = position::Position::new(input, 0).unwrap();
982 assert!(start.at_start());
983 assert!(end.at_end());
984
985 let error: Error<u32> = Error::new_from_span(
986 ErrorVariant::CustomError {
987 message: "error: empty".to_owned(),
988 },
989 start.span(&end),
990 );
991
992 assert_eq!(
993 format!("{}", error),
994 [
995 " --> 1:1",
996 " |",
997 "1 | ",
998 " | ^",
999 " |",
1000 " = error: empty"
1001 ]
1002 .join("\n")
1003 );
1004 }
1005
1006 #[test]
1007 fn mapped_parsing_error() {
1008 let input = "ab\ncd\nef";
1009 let pos = position::Position::new(input, 4).unwrap();
1010 let error: Error<u32> = Error::new_from_pos(
1011 ErrorVariant::ParsingError {
1012 positives: vec![1, 2, 3],
1013 negatives: vec![4, 5, 6],
1014 },
1015 pos,
1016 )
1017 .renamed_rules(|n| format!("{}", n + 1));
1018
1019 assert_eq!(
1020 format!("{}", error),
1021 [
1022 " --> 2:2",
1023 " |",
1024 "2 | cd",
1025 " | ^---",
1026 " |",
1027 " = unexpected 5, 6, or 7; expected 2, 3, or 4"
1028 ]
1029 .join("\n")
1030 );
1031 }
1032
1033 #[test]
1034 fn error_with_path() {
1035 let input = "ab\ncd\nef";
1036 let pos = position::Position::new(input, 4).unwrap();
1037 let error: Error<u32> = Error::new_from_pos(
1038 ErrorVariant::ParsingError {
1039 positives: vec![1, 2, 3],
1040 negatives: vec![4, 5, 6],
1041 },
1042 pos,
1043 )
1044 .with_path("file.rs");
1045
1046 assert_eq!(
1047 format!("{}", error),
1048 [
1049 " --> file.rs:2:2",
1050 " |",
1051 "2 | cd",
1052 " | ^---",
1053 " |",
1054 " = unexpected 4, 5, or 6; expected 1, 2, or 3"
1055 ]
1056 .join("\n")
1057 );
1058 }
1059
1060 #[test]
1061 fn underline_with_tabs() {
1062 let input = "a\txbc";
1063 let pos = position::Position::new(input, 2).unwrap();
1064 let error: Error<u32> = Error::new_from_pos(
1065 ErrorVariant::ParsingError {
1066 positives: vec![1, 2, 3],
1067 negatives: vec![4, 5, 6],
1068 },
1069 pos,
1070 )
1071 .with_path("file.rs");
1072
1073 assert_eq!(
1074 format!("{}", error),
1075 [
1076 " --> file.rs:1:3",
1077 " |",
1078 "1 | a xbc",
1079 " | ^---",
1080 " |",
1081 " = unexpected 4, 5, or 6; expected 1, 2, or 3"
1082 ]
1083 .join("\n")
1084 );
1085 }
1086
1087 #[test]
1088 fn pos_to_lcl_conversion() {
1089 let input = "input";
1090
1091 let pos = Position::new(input, 2).unwrap();
1092
1093 assert_eq!(LineColLocation::Pos(pos.line_col()), pos.into());
1094 }
1095
1096 #[test]
1097 fn span_to_lcl_conversion() {
1098 let input = "input";
1099
1100 let span = Span::new(input, 2, 4).unwrap();
1101 let (start, end) = span.split();
1102
1103 assert_eq!(
1104 LineColLocation::Span(start.line_col(), end.line_col()),
1105 span.into()
1106 );
1107 }
1108}