1use thiserror::Error;
2
3#[derive(Clone, Debug, Error)]
4enum ParseErrorReason {
5 #[error("extra unparsed text after match")]
6 Extra,
7 #[error("line() can't match here because this is not at the start of a line")]
8 NotAtLineStart,
9 #[error("section() can't match here because this is not at the start of a section")]
10 NotAtSectionStart,
11 #[error("line(pattern) matched part of the line, but not all of it")]
12 LineExtra,
13 #[error("section(pattern) matched part of the section, but not all of it")]
14 SectionExtra,
15 #[error("expected {0}")]
16 Expected(String),
17 #[error("failed to parse {input:?} as type {type_name}: {message}")]
18 FromStrFailed {
19 input: String,
20 type_name: &'static str,
21 message: String,
22 },
23}
24
25#[derive(Clone)]
28pub struct ParseError {
29 pub source: String,
31
32 pub location: usize,
36
37 reason: ParseErrorReason,
38}
39
40impl std::fmt::Display for ParseError {
41 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42 let reason = &self.reason;
43 let source = &self.source;
44 if self.location == source.len() {
45 write!(f, "{reason} at end of input")
46 } else {
47 let p = self.location.min(source.len());
48 let line_start = match source[..p].rfind('\n') {
49 Some(i) => i + 1,
50 None => 0,
51 };
52 let line_num = source[..line_start].chars().filter(|c| *c == '\n').count() + 1;
53 let column_num = source[line_start..p].chars().count() + 1;
54 write!(f, "{reason} at line {line_num} column {column_num}")
55 }
56 }
57}
58
59impl std::fmt::Debug for ParseError {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 f.debug_struct("ParseError")
65 .field("source", &self.source)
66 .field("location", &self.location)
67 .field("reason", &self.reason)
68 .field("summary", &format!("{self}"))
69 .finish()
70 }
71}
72
73impl std::error::Error for ParseError {}
74
75impl ParseError {
76 fn new(source: &str, location: usize, reason: ParseErrorReason) -> Self {
77 assert!(source.is_char_boundary(location));
78 ParseError {
79 source: source.to_string(),
80 location,
81 reason,
82 }
83 }
84
85 pub(crate) fn new_extra(source: &str, location: usize) -> Self {
86 Self::new(source, location, ParseErrorReason::Extra)
87 }
88
89 pub(crate) fn new_bad_line_start(source: &str, location: usize) -> Self {
90 Self::new(source, location, ParseErrorReason::NotAtLineStart)
91 }
92
93 pub(crate) fn new_bad_section_start(source: &str, location: usize) -> Self {
94 Self::new(source, location, ParseErrorReason::NotAtSectionStart)
95 }
96
97 pub(crate) fn new_line_extra(source: &str, location: usize) -> Self {
98 Self::new(source, location, ParseErrorReason::LineExtra)
99 }
100
101 pub(crate) fn new_section_extra(source: &str, location: usize) -> Self {
102 Self::new(source, location, ParseErrorReason::SectionExtra)
103 }
104
105 pub(crate) fn new_expected(source: &str, location: usize, expected: &str) -> Self {
106 Self::new(
107 source,
108 location,
109 ParseErrorReason::Expected(expected.to_string()),
110 )
111 }
112
113 pub(crate) fn new_from_str_failed(
114 source: &str,
115 start: usize,
116 end: usize,
117 type_name: &'static str,
118 message: String,
119 ) -> Self {
120 Self::new(
121 source,
122 start,
123 ParseErrorReason::FromStrFailed {
124 input: source[start..end].to_string(),
125 type_name,
126 message,
127 },
128 )
129 }
130
131 pub(crate) fn adjust_location(mut self, full_source: &str, offset: usize) -> Self {
136 self.source = full_source.to_string();
137 self.location += offset;
138 self
139 }
140}
141
142pub(crate) type Result<T, E = ParseError> = std::result::Result<T, E>;