1use crate::AnreError;
8
9struct SnippetRange {
17 prefix: bool,
18 suffix: bool,
19 selection_start: usize,
20 selection_length: usize,
21 offset: usize,
22 length: usize,
23}
24
25fn calculate_snippet_range(
26 original_selection_start: usize,
27 original_selection_length: usize,
28 source_total_length: usize,
29) -> SnippetRange {
30 const LEADING_LENGTH: usize = 15;
31 const SNIPPET_LENGTH: usize = 40;
32
33 let (prefix, selection_start, offset) =
34 if source_total_length < SNIPPET_LENGTH || original_selection_start < LEADING_LENGTH {
35 (false, 0, original_selection_start)
36 } else if original_selection_start + SNIPPET_LENGTH > source_total_length {
37 (
38 true,
39 source_total_length - SNIPPET_LENGTH,
40 original_selection_start - (source_total_length - SNIPPET_LENGTH),
41 )
42 } else {
43 (
44 true,
45 original_selection_start - LEADING_LENGTH,
46 LEADING_LENGTH,
47 )
48 };
49
50 let (suffix, selection_length) = if selection_start + SNIPPET_LENGTH >= source_total_length {
51 (false, source_total_length - selection_start)
52 } else {
53 (true, SNIPPET_LENGTH)
54 };
55
56 let length = if offset + original_selection_length > selection_length {
57 selection_length - offset
58 } else {
59 original_selection_length
60 };
61
62 SnippetRange {
63 prefix,
64 suffix,
65 selection_start,
66 selection_length,
67 offset,
68 length,
69 }
70}
71
72fn generate_snippet_and_indented_detail(
73 chars: &mut dyn Iterator<Item = char>,
74 snippet_range: &SnippetRange,
75 detail: &str,
76) -> (String, String) {
77 let mut snippet = String::new();
79 snippet.push_str("| ");
80 if snippet_range.prefix {
81 snippet.push_str("...");
82 }
83 let selection_chars = chars
84 .skip(snippet_range.selection_start)
85 .take(snippet_range.selection_length);
86 let selection_string = selection_chars
87 .map(|c| match c {
88 '\n' => ' ',
89 '\t' => ' ',
90 _ => c,
91 })
92 .collect::<String>();
93 snippet.push_str(&selection_string);
94 if snippet_range.suffix {
95 snippet.push_str("...");
96 }
97
98 let mut indented_detail = String::new();
100 indented_detail.push_str("| ");
101 if snippet_range.prefix {
102 indented_detail.push_str(" ");
103 }
104 indented_detail.push_str(&" ".repeat(snippet_range.offset));
105 indented_detail.push('^');
106 if snippet_range.length > 0 {
107 indented_detail.push_str(&"^".repeat(snippet_range.length - 1));
108 } else {
109 indented_detail.push_str("____");
110 }
111 indented_detail.push(' ');
112 indented_detail.push_str(detail);
113
114 (snippet, indented_detail)
115}
116
117impl AnreError {
118 pub fn with_source(&self, source: &str) -> String {
119 let source_total_length = source.chars().count();
122 let mut chars = source.chars();
123
124 match self {
133 AnreError::SyntaxIncorrect(msg) => msg.to_owned(),
134 AnreError::UnexpectedEndOfDocument(detail) => {
135 let msg = "Unexpected to reach the end of document.";
136 let snippet_range =
137 calculate_snippet_range(source_total_length, 0, source_total_length);
138 let (snippet, indented_detail) =
139 generate_snippet_and_indented_detail(&mut chars, &snippet_range, detail);
140 format!("{}\n{}\n{}", msg, snippet, indented_detail)
141 }
142 AnreError::MessageWithLocation(detail, location) => {
143 let msg = format!(
144 "Error at line: {}, column: {}",
145 location.line + 1,
146 location.column + 1
147 );
148
149 let snippet_range =
150 calculate_snippet_range(location.index, location.length, source_total_length);
151 let (snippet, indented_detail) =
152 generate_snippet_and_indented_detail(&mut chars, &snippet_range, detail);
153 format!("{}\n{}\n{}", msg, snippet, indented_detail)
154 }
155 }
156 }
157}
158
159#[cfg(test)]
160mod tests {
161
162 use pretty_assertions::assert_eq;
163
164 use crate::{AnreError, location::Location};
165
166 #[test]
167 fn test_error_with_source() {
168 let source1 = "0123456789"; let source2 = "012345678_b12345678_c12345678_d12345678_e123456789"; let msg = "abcde";
171
172 assert_eq!(AnreError::SyntaxIncorrect(msg.to_owned()).with_source(source1), msg);
173 assert_eq!(AnreError::SyntaxIncorrect(msg.to_owned()).with_source(source2), msg);
174 }
175
176 #[test]
177 fn test_error_with_source_and_unexpected_end_of_document() {
178 let source1 = "0123456789"; let source2 = "012345678_b12345678_c12345678_d12345678_e123456789"; let msg = "abcde";
181
182 assert_eq!(
183 AnreError::UnexpectedEndOfDocument(msg.to_owned()).with_source(source1),
184 r#"Unexpected to reach the end of document.
185| 0123456789
186| ^____ abcde"#
187 );
188
189 assert_eq!(
190 AnreError::UnexpectedEndOfDocument(msg.to_owned()).with_source(source2),
191 r#"Unexpected to reach the end of document.
192| ...b12345678_c12345678_d12345678_e123456789
193| ^____ abcde"#
194 );
195 }
196
197 #[test]
198 fn test_error_with_source_and_location() {
199 let source1 = "0123456789"; let source2 = "012345678_b12345678_c12345678_d12345678_e123456789"; let msg = "abcde";
202
203 assert_eq!(
206 AnreError::MessageWithLocation(msg.to_owned(), Location::new_position(0, 11, 13))
207 .with_source(source1),
208 r#"Error at line: 12, column: 14
209| 0123456789
210| ^____ abcde"#
211 );
212
213 assert_eq!(
214 AnreError::MessageWithLocation(msg.to_owned(), Location::new_position(0, 11, 13))
215 .with_source(source2),
216 r#"Error at line: 12, column: 14
217| 012345678_b12345678_c12345678_d12345678_...
218| ^____ abcde"#
219 );
220
221 assert_eq!(
224 AnreError::MessageWithLocation(msg.to_owned(), Location::new_position(2, 11, 13))
225 .with_source(source1),
226 r#"Error at line: 12, column: 14
227| 0123456789
228| ^____ abcde"#
229 );
230
231 assert_eq!(
232 AnreError::MessageWithLocation(msg.to_owned(), Location::new_position(15, 11, 13))
233 .with_source(source2),
234 r#"Error at line: 12, column: 14
235| ...b12345678_c12345678_d12345678_e123456789
236| ^____ abcde"#
237 );
238
239 assert_eq!(
242 AnreError::MessageWithLocation(msg.to_owned(), Location::new_position(5, 11, 13))
243 .with_source(source1),
244 r#"Error at line: 12, column: 14
245| 0123456789
246| ^____ abcde"#
247 );
248
249 assert_eq!(
250 AnreError::MessageWithLocation(msg.to_owned(), Location::new_position(25, 11, 13))
251 .with_source(source2),
252 r#"Error at line: 12, column: 14
253| ...b12345678_c12345678_d12345678_e123456789
254| ^____ abcde"#
255 );
256
257 assert_eq!(
260 AnreError::MessageWithLocation(msg.to_owned(), Location::new_position(8, 11, 13))
261 .with_source(source1),
262 r#"Error at line: 12, column: 14
263| 0123456789
264| ^____ abcde"#
265 );
266
267 assert_eq!(
268 AnreError::MessageWithLocation(msg.to_owned(), Location::new_position(45, 11, 13))
269 .with_source(source2),
270 r#"Error at line: 12, column: 14
271| ...b12345678_c12345678_d12345678_e123456789
272| ^____ abcde"#
273 );
274
275 assert_eq!(
278 AnreError::MessageWithLocation(msg.to_owned(), Location::new_position(10, 11, 13))
279 .with_source(source1),
280 r#"Error at line: 12, column: 14
281| 0123456789
282| ^____ abcde"#
283 );
284
285 assert_eq!(
286 AnreError::MessageWithLocation(msg.to_owned(), Location::new_position(50, 11, 13))
287 .with_source(source2),
288 r#"Error at line: 12, column: 14
289| ...b12345678_c12345678_d12345678_e123456789
290| ^____ abcde"#
291 );
292 }
293
294 #[test]
295 fn test_error_with_source_and_range() {
296 let source1 = "0123456789"; let source2 = "012345678_b12345678_c12345678_d12345678_e123456789"; let msg = "abcde";
299
300 assert_eq!(
303 AnreError::MessageWithLocation(msg.to_owned(), Location::new_range(0, 17, 19, 4))
304 .with_source(source1),
305 r#"Error at line: 18, column: 20
306| 0123456789
307| ^^^^ abcde"#
308 );
309
310 assert_eq!(
311 AnreError::MessageWithLocation(msg.to_owned(), Location::new_range(0, 17, 19, 8))
312 .with_source(source2),
313 r#"Error at line: 18, column: 20
314| 012345678_b12345678_c12345678_d12345678_...
315| ^^^^^^^^ abcde"#
316 );
317
318 assert_eq!(
321 AnreError::MessageWithLocation(msg.to_owned(), Location::new_range(2, 17, 19, 4))
322 .with_source(source1),
323 r#"Error at line: 18, column: 20
324| 0123456789
325| ^^^^ abcde"#
326 );
327
328 assert_eq!(
329 AnreError::MessageWithLocation(msg.to_owned(), Location::new_range(15, 17, 19, 8))
330 .with_source(source2),
331 r#"Error at line: 18, column: 20
332| ...b12345678_c12345678_d12345678_e123456789
333| ^^^^^^^^ abcde"#
334 );
335
336 assert_eq!(
339 AnreError::MessageWithLocation(msg.to_owned(), Location::new_range(5, 17, 19, 4))
340 .with_source(source1),
341 r#"Error at line: 18, column: 20
342| 0123456789
343| ^^^^ abcde"#
344 );
345
346 assert_eq!(
347 AnreError::MessageWithLocation(msg.to_owned(), Location::new_range(25, 17, 19, 8))
348 .with_source(source2),
349 r#"Error at line: 18, column: 20
350| ...b12345678_c12345678_d12345678_e123456789
351| ^^^^^^^^ abcde"#
352 );
353
354 assert_eq!(
357 AnreError::MessageWithLocation(msg.to_owned(), Location::new_range(8, 17, 19, 4))
358 .with_source(source1),
359 r#"Error at line: 18, column: 20
360| 0123456789
361| ^^ abcde"#
362 );
363
364 assert_eq!(
365 AnreError::MessageWithLocation(msg.to_owned(), Location::new_range(45, 17, 19, 8))
366 .with_source(source2),
367 r#"Error at line: 18, column: 20
368| ...b12345678_c12345678_d12345678_e123456789
369| ^^^^^ abcde"#
370 );
371
372 assert_eq!(
375 AnreError::MessageWithLocation(msg.to_owned(), Location::new_range(10, 17, 19, 4))
376 .with_source(source1),
377 r#"Error at line: 18, column: 20
378| 0123456789
379| ^____ abcde"#
380 );
381
382 assert_eq!(
383 AnreError::MessageWithLocation(msg.to_owned(), Location::new_range(50, 17, 19, 8))
384 .with_source(source2),
385 r#"Error at line: 18, column: 20
386| ...b12345678_c12345678_d12345678_e123456789
387| ^____ abcde"#
388 );
389 }
390}