1use std::{
2 fmt::{Display, Formatter},
3 io::stderr,
4};
5
6use crate::location::Location;
7
8#[derive(Clone, Debug, Default, PartialEq)]
9pub struct ErrorContext {
10 pub start_end: Option<(Location, Location)>,
11 pub file_name: Option<String>,
12 pub file_content: Option<String>,
13}
14
15#[derive(Clone, Debug, PartialEq)]
16pub struct Error {
17 pub kind: ErrorKind,
18 pub context: Option<Box<ErrorContext>>,
19}
20
21impl Error {
22 pub fn context_loc(self, start: Location, end: Location) -> Self {
25 let mut context = self.context.unwrap_or_default();
26 context.start_end.get_or_insert((start, end));
27
28 Error {
29 kind: self.kind,
30 context: Some(context),
31 }
32 }
33
34 pub fn context_file_name(self, file_name: String) -> Self {
37 let mut context = self.context.unwrap_or_default();
38 context.file_name.get_or_insert(file_name);
39
40 Error {
41 kind: self.kind,
42 context: Some(context),
43 }
44 }
45
46 pub fn context_file_content(self, file_content: String) -> Self {
49 let mut context = self.context.unwrap_or_default();
50 context.file_content.get_or_insert(file_content);
51
52 Error {
53 kind: self.kind,
54 context: Some(context),
55 }
56 }
57
58 pub fn start(&self) -> Option<Location> {
61 self.context
62 .as_ref()
63 .and_then(|c| c.start_end)
64 .map(|se| se.0)
65 }
66
67 pub fn end(&self) -> Option<Location> {
70 self.context
71 .as_ref()
72 .and_then(|c| c.start_end)
73 .map(|se| se.1)
74 }
75}
76
77impl From<std::io::Error> for Error {
78 fn from(e: std::io::Error) -> Self {
79 Error {
80 kind: ErrorKind::IoError(e.to_string()),
81 context: None,
82 }
83 }
84}
85
86#[cfg(feature = "serde")]
87impl serde::de::Error for Error {
88 fn custom<T>(msg: T) -> Self
89 where
90 T: Display,
91 {
92 Error {
93 kind: ErrorKind::Custom(msg.to_string()),
94 context: None,
95 }
96 }
97}
98
99#[cfg(feature = "serde")]
100impl serde::ser::Error for Error {
101 fn custom<T>(msg: T) -> Self
102 where
103 T: Display,
104 {
105 Error {
106 kind: ErrorKind::Custom(msg.to_string()),
107 context: None,
108 }
109 }
110}
111
112impl Display for Error {
113 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
114 match self.context.as_ref() {
116 Some(context) => match (
117 context.start_end.as_ref(),
118 context.file_name.as_ref(),
119 context.file_content.as_ref(),
120 ) {
121 (Some((start, _)), _, _) => {
122 write!(f, "error at {}: {}", start, self.kind)
123 }
124 (_, _, _) => {
125 write!(f, "error: {}", self.kind)
126 }
127 },
128 None => write!(f, "error: {}", self.kind),
129 }
130 }
131}
132
133impl std::error::Error for Error {}
134
135pub fn print_error(e: &Error) -> std::io::Result<()> {
136 use std::io::Write;
137
138 let f = stderr();
139 let mut f = f.lock();
140 match e.context.as_ref() {
141 Some(context) => match (
142 context.start_end.as_ref(),
143 context.file_name.as_ref(),
144 context.file_content.as_ref(),
145 ) {
146 (Some((start, end)), file_name, Some(file_content)) => {
147 let max_line_col_width = start.line.max(end.line).to_string().len();
148 let col_ws_rep = " ".repeat(max_line_col_width);
149 writeln!(f, "error: {}", e.kind)?;
150 writeln!(
151 f,
152 "{}--> {}:{}:{}",
153 col_ws_rep,
154 file_name.map(AsRef::as_ref).unwrap_or("string"),
155 start.line,
156 start.column
157 )?;
158
159 writeln!(f, "{} |", col_ws_rep)?;
160 let mut lines = file_content.lines().skip(start.line as usize - 1);
161 let start_line_string = start.line.to_string();
162 let start_line_padding = " ".repeat(max_line_col_width - start_line_string.len());
163
164 if start.line == end.line {
165 writeln!(
167 f,
168 "{}{} | {}",
169 start_line_padding,
170 start.line,
171 lines.next().unwrap_or_default()
172 )?;
173 writeln!(
175 f,
176 "{} | {}{}",
177 col_ws_rep,
178 " ".repeat(start.column as usize - 1),
179 "^".repeat((end.column - start.column) as usize)
180 )?;
181 } else {
182 writeln!(
184 f,
185 "{}{} | {}",
186 start_line_padding,
187 start.line,
188 lines.next().unwrap_or_default()
189 )?;
190 writeln!(
191 f,
192 "{} | {}^",
193 col_ws_rep,
194 "_".repeat((start.column - 1) as usize),
195 )?;
196 for line_number in start.line + 1..=end.line {
197 let line_nr_string = line_number.to_string();
198 let line_padding = " ".repeat(max_line_col_width - line_nr_string.len());
199 writeln!(
200 f,
201 "{}{} | | {}",
202 line_padding,
203 line_nr_string,
204 lines.next().unwrap_or_default()
205 )?;
206 }
207
208 writeln!(
209 f,
210 "{} | |{}^",
211 col_ws_rep,
212 "_".repeat((end.column - 1) as usize)
213 )?;
214 }
215
216 writeln!(f, "{} |", col_ws_rep)
217 }
218 (_, Some(file_name), _) => writeln!(f, "file \"{}\": {}", file_name, e),
219 _ => writeln!(f, "{}", e),
220 },
221 _ => writeln!(f, "{}", e),
222 }
223}
224
225#[derive(Clone, Debug, PartialEq)]
226#[non_exhaustive]
227pub enum ErrorKind {
228 ExpectedBool,
229 ExpectedString,
230 ExpectedStrGotEscapes,
231 ExpectedList,
232
233 ParseError(String),
234
235 IoError(String),
236 Custom(String),
237}
238
239impl Display for ErrorKind {
240 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
241 match self {
242 ErrorKind::ExpectedBool => write!(f, "expected bool"),
243 ErrorKind::ExpectedStrGotEscapes => {
244 write!(f, "expected zero-copy string which doesn't support escapes")
245 }
246 ErrorKind::ExpectedString => write!(f, "expected string"),
247 ErrorKind::ExpectedList => write!(f, "expected list"),
248 ErrorKind::ParseError(e) => write!(f, "parsing error: {}", e),
249 ErrorKind::IoError(e) => write!(f, "io error: {}", e),
250 ErrorKind::Custom(s) => write!(f, "{}", s),
251 }
252 }
253}