1 // pest. The Elegant Parser
2 // Copyright (c) 2018 Dragoș Tiselice
3 //
4 // Licensed under the Apache License, Version 2.0
5 // <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
6 // license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7 // option. All files in the project carrying such notice may not be copied,
8 // modified, or distributed except according to those terms.
9
10 //! Types for different kinds of parsing failures.
11
12 use alloc::borrow::Cow;
13 use alloc::borrow::ToOwned;
14 use alloc::format;
15 use alloc::string::String;
16 use alloc::string::ToString;
17 use alloc::vec::Vec;
18 use core::cmp;
19 use core::fmt;
20 use core::mem;
21
22 use crate::position::Position;
23 use crate::span::Span;
24 use crate::RuleType;
25
26 /// Parse-related error type.
27 #[derive(Clone, Debug, Eq, Hash, PartialEq)]
28 #[cfg_attr(feature = "std", derive(thiserror::Error))]
29 pub struct Error<R> {
30 /// Variant of the error
31 pub variant: ErrorVariant<R>,
32 /// Location within the input string
33 pub location: InputLocation,
34 /// Line/column within the input string
35 pub line_col: LineColLocation,
36 path: Option<String>,
37 line: String,
38 continued_line: Option<String>,
39 }
40
41 /// Different kinds of parsing errors.
42 #[derive(Clone, Debug, Eq, Hash, PartialEq)]
43 #[cfg_attr(feature = "std", derive(thiserror::Error))]
44 pub enum ErrorVariant<R> {
45 /// Generated parsing error with expected and unexpected `Rule`s
46 ParsingError {
47 /// Positive attempts
48 positives: Vec<R>,
49 /// Negative attempts
50 negatives: Vec<R>,
51 },
52 /// Custom error with a message
53 CustomError {
54 /// Short explanation
55 message: String,
56 },
57 }
58
59 /// Where an `Error` has occurred.
60 #[derive(Clone, Debug, Eq, Hash, PartialEq)]
61 pub enum InputLocation {
62 /// `Error` was created by `Error::new_from_pos`
63 Pos(usize),
64 /// `Error` was created by `Error::new_from_span`
65 Span((usize, usize)),
66 }
67
68 /// Line/column where an `Error` has occurred.
69 #[derive(Clone, Debug, Eq, Hash, PartialEq)]
70 pub enum LineColLocation {
71 /// Line/column pair if `Error` was created by `Error::new_from_pos`
72 Pos((usize, usize)),
73 /// Line/column pairs if `Error` was created by `Error::new_from_span`
74 Span((usize, usize), (usize, usize)),
75 }
76
77 impl<R: RuleType> Error<R> {
78 /// Creates `Error` from `ErrorVariant` and `Position`.
79 ///
80 /// # Examples
81 ///
82 /// ```
83 /// # use pest::error::{Error, ErrorVariant};
84 /// # use pest::Position;
85 /// # #[allow(non_camel_case_types)]
86 /// # #[allow(dead_code)]
87 /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
88 /// # enum Rule {
89 /// # open_paren,
90 /// # closed_paren
91 /// # }
92 /// # let input = "";
93 /// # let pos = Position::from_start(input);
94 /// let error = Error::new_from_pos(
95 /// ErrorVariant::ParsingError {
96 /// positives: vec![Rule::open_paren],
97 /// negatives: vec![Rule::closed_paren]
98 /// },
99 /// pos
100 /// );
101 ///
102 /// println!("{}", error);
103 /// ```
new_from_pos(variant: ErrorVariant<R>, pos: Position<'_>) -> Error<R>104 pub fn new_from_pos(variant: ErrorVariant<R>, pos: Position<'_>) -> Error<R> {
105 let visualize_ws = pos.match_char('\n') || pos.match_char('\r');
106 let line_of = pos.line_of();
107 let line = if visualize_ws {
108 visualize_whitespace(line_of)
109 } else {
110 line_of.replace(&['\r', '\n'][..], "")
111 };
112 Error {
113 variant,
114 location: InputLocation::Pos(pos.pos()),
115 path: None,
116 line,
117 continued_line: None,
118 line_col: LineColLocation::Pos(pos.line_col()),
119 }
120 }
121
122 /// Creates `Error` from `ErrorVariant` and `Span`.
123 ///
124 /// # Examples
125 ///
126 /// ```
127 /// # use pest::error::{Error, ErrorVariant};
128 /// # use pest::{Position, Span};
129 /// # #[allow(non_camel_case_types)]
130 /// # #[allow(dead_code)]
131 /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
132 /// # enum Rule {
133 /// # open_paren,
134 /// # closed_paren
135 /// # }
136 /// # let input = "";
137 /// # let start = Position::from_start(input);
138 /// # let end = start.clone();
139 /// # let span = start.span(&end);
140 /// let error = Error::new_from_span(
141 /// ErrorVariant::ParsingError {
142 /// positives: vec![Rule::open_paren],
143 /// negatives: vec![Rule::closed_paren]
144 /// },
145 /// span
146 /// );
147 ///
148 /// println!("{}", error);
149 /// ```
new_from_span(variant: ErrorVariant<R>, span: Span<'_>) -> Error<R>150 pub fn new_from_span(variant: ErrorVariant<R>, span: Span<'_>) -> Error<R> {
151 let end = span.end_pos();
152 let mut end_line_col = end.line_col();
153 // end position is after a \n, so we want to point to the visual lf symbol
154 if end_line_col.1 == 1 {
155 let mut visual_end = end;
156 visual_end.skip_back(1);
157 let lc = visual_end.line_col();
158 end_line_col = (lc.0, lc.1 + 1);
159 };
160
161 let mut line_iter = span.lines();
162 let sl = line_iter.next().unwrap_or("");
163 let mut chars = span.as_str().chars();
164 let visualize_ws = matches!(chars.next(), Some('\n') | Some('\r'))
165 || matches!(chars.last(), Some('\n') | Some('\r'));
166 let start_line = if visualize_ws {
167 visualize_whitespace(sl)
168 } else {
169 sl.to_owned().replace(&['\r', '\n'][..], "")
170 };
171 let ll = line_iter.last();
172 let continued_line = if visualize_ws {
173 ll.map(str::to_owned)
174 } else {
175 ll.map(visualize_whitespace)
176 };
177
178 Error {
179 variant,
180 location: InputLocation::Span((span.start(), end.pos())),
181 path: None,
182 line: start_line,
183 continued_line,
184 line_col: LineColLocation::Span(span.start_pos().line_col(), end_line_col),
185 }
186 }
187
188 /// Returns `Error` variant with `path` which is shown when formatted with `Display`.
189 ///
190 /// # Examples
191 ///
192 /// ```
193 /// # use pest::error::{Error, ErrorVariant};
194 /// # use pest::Position;
195 /// # #[allow(non_camel_case_types)]
196 /// # #[allow(dead_code)]
197 /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
198 /// # enum Rule {
199 /// # open_paren,
200 /// # closed_paren
201 /// # }
202 /// # let input = "";
203 /// # let pos = Position::from_start(input);
204 /// Error::new_from_pos(
205 /// ErrorVariant::ParsingError {
206 /// positives: vec![Rule::open_paren],
207 /// negatives: vec![Rule::closed_paren]
208 /// },
209 /// pos
210 /// ).with_path("file.rs");
211 /// ```
with_path(mut self, path: &str) -> Error<R>212 pub fn with_path(mut self, path: &str) -> Error<R> {
213 self.path = Some(path.to_owned());
214
215 self
216 }
217
218 /// Returns the path set using [`Error::with_path()`].
219 ///
220 /// # Examples
221 ///
222 /// ```
223 /// # use pest::error::{Error, ErrorVariant};
224 /// # use pest::Position;
225 /// # #[allow(non_camel_case_types)]
226 /// # #[allow(dead_code)]
227 /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
228 /// # enum Rule {
229 /// # open_paren,
230 /// # closed_paren
231 /// # }
232 /// # let input = "";
233 /// # let pos = Position::from_start(input);
234 /// # let error = Error::new_from_pos(
235 /// # ErrorVariant::ParsingError {
236 /// # positives: vec![Rule::open_paren],
237 /// # negatives: vec![Rule::closed_paren]
238 /// # },
239 /// # pos);
240 /// let error = error.with_path("file.rs");
241 /// assert_eq!(Some("file.rs"), error.path());
242 /// ```
path(&self) -> Option<&str>243 pub fn path(&self) -> Option<&str> {
244 self.path.as_deref()
245 }
246
247 /// Returns the line that the error is on.
line(&self) -> &str248 pub fn line(&self) -> &str {
249 self.line.as_str()
250 }
251
252 /// Renames all `Rule`s if this is a [`ParsingError`]. It does nothing when called on a
253 /// [`CustomError`].
254 ///
255 /// Useful in order to rename verbose rules or have detailed per-`Rule` formatting.
256 ///
257 /// [`ParsingError`]: enum.ErrorVariant.html#variant.ParsingError
258 /// [`CustomError`]: enum.ErrorVariant.html#variant.CustomError
259 ///
260 /// # Examples
261 ///
262 /// ```
263 /// # use pest::error::{Error, ErrorVariant};
264 /// # use pest::Position;
265 /// # #[allow(non_camel_case_types)]
266 /// # #[allow(dead_code)]
267 /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
268 /// # enum Rule {
269 /// # open_paren,
270 /// # closed_paren
271 /// # }
272 /// # let input = "";
273 /// # let pos = Position::from_start(input);
274 /// Error::new_from_pos(
275 /// ErrorVariant::ParsingError {
276 /// positives: vec![Rule::open_paren],
277 /// negatives: vec![Rule::closed_paren]
278 /// },
279 /// pos
280 /// ).renamed_rules(|rule| {
281 /// match *rule {
282 /// Rule::open_paren => "(".to_owned(),
283 /// Rule::closed_paren => "closed paren".to_owned()
284 /// }
285 /// });
286 /// ```
renamed_rules<F>(mut self, f: F) -> Error<R> where F: FnMut(&R) -> String,287 pub fn renamed_rules<F>(mut self, f: F) -> Error<R>
288 where
289 F: FnMut(&R) -> String,
290 {
291 let variant = match self.variant {
292 ErrorVariant::ParsingError {
293 positives,
294 negatives,
295 } => {
296 let message = Error::parsing_error_message(&positives, &negatives, f);
297 ErrorVariant::CustomError { message }
298 }
299 variant => variant,
300 };
301
302 self.variant = variant;
303
304 self
305 }
306
start(&self) -> (usize, usize)307 fn start(&self) -> (usize, usize) {
308 match self.line_col {
309 LineColLocation::Pos(line_col) => line_col,
310 LineColLocation::Span(start_line_col, _) => start_line_col,
311 }
312 }
313
spacing(&self) -> String314 fn spacing(&self) -> String {
315 let line = match self.line_col {
316 LineColLocation::Pos((line, _)) => line,
317 LineColLocation::Span((start_line, _), (end_line, _)) => cmp::max(start_line, end_line),
318 };
319
320 let line_str_len = format!("{}", line).len();
321
322 let mut spacing = String::new();
323 for _ in 0..line_str_len {
324 spacing.push(' ');
325 }
326
327 spacing
328 }
329
underline(&self) -> String330 fn underline(&self) -> String {
331 let mut underline = String::new();
332
333 let mut start = self.start().1;
334 let end = match self.line_col {
335 LineColLocation::Span(_, (_, mut end)) => {
336 let inverted_cols = start > end;
337 if inverted_cols {
338 mem::swap(&mut start, &mut end);
339 start -= 1;
340 end += 1;
341 }
342
343 Some(end)
344 }
345 _ => None,
346 };
347 let offset = start - 1;
348 let line_chars = self.line.chars();
349
350 for c in line_chars.take(offset) {
351 match c {
352 '\t' => underline.push('\t'),
353 _ => underline.push(' '),
354 }
355 }
356
357 if let Some(end) = end {
358 underline.push('^');
359 if end - start > 1 {
360 for _ in 2..(end - start) {
361 underline.push('-');
362 }
363 underline.push('^');
364 }
365 } else {
366 underline.push_str("^---")
367 }
368
369 underline
370 }
371
message(&self) -> String372 fn message(&self) -> String {
373 self.variant.message().to_string()
374 }
375
parsing_error_message<F>(positives: &[R], negatives: &[R], mut f: F) -> String where F: FnMut(&R) -> String,376 fn parsing_error_message<F>(positives: &[R], negatives: &[R], mut f: F) -> String
377 where
378 F: FnMut(&R) -> String,
379 {
380 match (negatives.is_empty(), positives.is_empty()) {
381 (false, false) => format!(
382 "unexpected {}; expected {}",
383 Error::enumerate(negatives, &mut f),
384 Error::enumerate(positives, &mut f)
385 ),
386 (false, true) => format!("unexpected {}", Error::enumerate(negatives, &mut f)),
387 (true, false) => format!("expected {}", Error::enumerate(positives, &mut f)),
388 (true, true) => "unknown parsing error".to_owned(),
389 }
390 }
391
enumerate<F>(rules: &[R], f: &mut F) -> String where F: FnMut(&R) -> String,392 fn enumerate<F>(rules: &[R], f: &mut F) -> String
393 where
394 F: FnMut(&R) -> String,
395 {
396 match rules.len() {
397 1 => f(&rules[0]),
398 2 => format!("{} or {}", f(&rules[0]), f(&rules[1])),
399 l => {
400 let non_separated = f(&rules[l - 1]);
401 let separated = rules
402 .iter()
403 .take(l - 1)
404 .map(f)
405 .collect::<Vec<_>>()
406 .join(", ");
407 format!("{}, or {}", separated, non_separated)
408 }
409 }
410 }
411
format(&self) -> String412 pub(crate) fn format(&self) -> String {
413 let spacing = self.spacing();
414 let path = self
415 .path
416 .as_ref()
417 .map(|path| format!("{}:", path))
418 .unwrap_or_default();
419
420 let pair = (self.line_col.clone(), &self.continued_line);
421 if let (LineColLocation::Span(_, end), &Some(ref continued_line)) = pair {
422 let has_line_gap = end.0 - self.start().0 > 1;
423 if has_line_gap {
424 format!(
425 "{s }--> {p}{ls}:{c}\n\
426 {s } |\n\
427 {ls:w$} | {line}\n\
428 {s } | ...\n\
429 {le:w$} | {continued_line}\n\
430 {s } | {underline}\n\
431 {s } |\n\
432 {s } = {message}",
433 s = spacing,
434 w = spacing.len(),
435 p = path,
436 ls = self.start().0,
437 le = end.0,
438 c = self.start().1,
439 line = self.line,
440 continued_line = continued_line,
441 underline = self.underline(),
442 message = self.message()
443 )
444 } else {
445 format!(
446 "{s }--> {p}{ls}:{c}\n\
447 {s } |\n\
448 {ls:w$} | {line}\n\
449 {le:w$} | {continued_line}\n\
450 {s } | {underline}\n\
451 {s } |\n\
452 {s } = {message}",
453 s = spacing,
454 w = spacing.len(),
455 p = path,
456 ls = self.start().0,
457 le = end.0,
458 c = self.start().1,
459 line = self.line,
460 continued_line = continued_line,
461 underline = self.underline(),
462 message = self.message()
463 )
464 }
465 } else {
466 format!(
467 "{s}--> {p}{l}:{c}\n\
468 {s} |\n\
469 {l} | {line}\n\
470 {s} | {underline}\n\
471 {s} |\n\
472 {s} = {message}",
473 s = spacing,
474 p = path,
475 l = self.start().0,
476 c = self.start().1,
477 line = self.line,
478 underline = self.underline(),
479 message = self.message()
480 )
481 }
482 }
483 }
484
485 impl<R: RuleType> ErrorVariant<R> {
486 ///
487 /// Returns the error message for [`ErrorVariant`]
488 ///
489 /// If [`ErrorVariant`] is [`CustomError`], it returns a
490 /// [`Cow::Borrowed`] reference to [`message`]. If [`ErrorVariant`] is [`ParsingError`], a
491 /// [`Cow::Owned`] containing "expected [ErrorVariant::ParsingError::positives] [ErrorVariant::ParsingError::negatives]" is returned.
492 ///
493 /// [`ErrorVariant`]: enum.ErrorVariant.html
494 /// [`CustomError`]: enum.ErrorVariant.html#variant.CustomError
495 /// [`ParsingError`]: enum.ErrorVariant.html#variant.ParsingError
496 /// [`Cow::Owned`]: https://doc.rust-lang.org/std/borrow/enum.Cow.html#variant.Owned
497 /// [`Cow::Borrowed`]: https://doc.rust-lang.org/std/borrow/enum.Cow.html#variant.Borrowed
498 /// [`message`]: enum.ErrorVariant.html#variant.CustomError.field.message
499 /// # Examples
500 ///
501 /// ```
502 /// # use pest::error::ErrorVariant;
503 /// let variant = ErrorVariant::<()>::CustomError {
504 /// message: String::from("unexpected error")
505 /// };
506 ///
507 /// println!("{}", variant.message());
message(&self) -> Cow<'_, str>508 pub fn message(&self) -> Cow<'_, str> {
509 match self {
510 ErrorVariant::ParsingError {
511 ref positives,
512 ref negatives,
513 } => Cow::Owned(Error::parsing_error_message(positives, negatives, |r| {
514 format!("{:?}", r)
515 })),
516 ErrorVariant::CustomError { ref message } => Cow::Borrowed(message),
517 }
518 }
519 }
520
521 impl<R: RuleType> fmt::Display for Error<R> {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result522 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
523 write!(f, "{}", self.format())
524 }
525 }
526
527 impl<R: RuleType> fmt::Display for ErrorVariant<R> {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result528 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
529 match self {
530 ErrorVariant::ParsingError { .. } => write!(f, "parsing error: {}", self.message()),
531 ErrorVariant::CustomError { .. } => write!(f, "{}", self.message()),
532 }
533 }
534 }
535
visualize_whitespace(input: &str) -> String536 fn visualize_whitespace(input: &str) -> String {
537 input.to_owned().replace('\r', "␍").replace('\n', "␊")
538 }
539
540 #[cfg(test)]
541 mod tests {
542 use super::super::position;
543 use super::*;
544 use alloc::vec;
545
546 #[test]
display_parsing_error_mixed()547 fn display_parsing_error_mixed() {
548 let input = "ab\ncd\nef";
549 let pos = position::Position::new(input, 4).unwrap();
550 let error: Error<u32> = Error::new_from_pos(
551 ErrorVariant::ParsingError {
552 positives: vec![1, 2, 3],
553 negatives: vec![4, 5, 6],
554 },
555 pos,
556 );
557
558 assert_eq!(
559 format!("{}", error),
560 vec![
561 " --> 2:2",
562 " |",
563 "2 | cd",
564 " | ^---",
565 " |",
566 " = unexpected 4, 5, or 6; expected 1, 2, or 3",
567 ]
568 .join("\n")
569 );
570 }
571
572 #[test]
display_parsing_error_positives()573 fn display_parsing_error_positives() {
574 let input = "ab\ncd\nef";
575 let pos = position::Position::new(input, 4).unwrap();
576 let error: Error<u32> = Error::new_from_pos(
577 ErrorVariant::ParsingError {
578 positives: vec![1, 2],
579 negatives: vec![],
580 },
581 pos,
582 );
583
584 assert_eq!(
585 format!("{}", error),
586 vec![
587 " --> 2:2",
588 " |",
589 "2 | cd",
590 " | ^---",
591 " |",
592 " = expected 1 or 2",
593 ]
594 .join("\n")
595 );
596 }
597
598 #[test]
display_parsing_error_negatives()599 fn display_parsing_error_negatives() {
600 let input = "ab\ncd\nef";
601 let pos = position::Position::new(input, 4).unwrap();
602 let error: Error<u32> = Error::new_from_pos(
603 ErrorVariant::ParsingError {
604 positives: vec![],
605 negatives: vec![4, 5, 6],
606 },
607 pos,
608 );
609
610 assert_eq!(
611 format!("{}", error),
612 vec![
613 " --> 2:2",
614 " |",
615 "2 | cd",
616 " | ^---",
617 " |",
618 " = unexpected 4, 5, or 6",
619 ]
620 .join("\n")
621 );
622 }
623
624 #[test]
display_parsing_error_unknown()625 fn display_parsing_error_unknown() {
626 let input = "ab\ncd\nef";
627 let pos = position::Position::new(input, 4).unwrap();
628 let error: Error<u32> = Error::new_from_pos(
629 ErrorVariant::ParsingError {
630 positives: vec![],
631 negatives: vec![],
632 },
633 pos,
634 );
635
636 assert_eq!(
637 format!("{}", error),
638 vec![
639 " --> 2:2",
640 " |",
641 "2 | cd",
642 " | ^---",
643 " |",
644 " = unknown parsing error",
645 ]
646 .join("\n")
647 );
648 }
649
650 #[test]
display_custom_pos()651 fn display_custom_pos() {
652 let input = "ab\ncd\nef";
653 let pos = position::Position::new(input, 4).unwrap();
654 let error: Error<u32> = Error::new_from_pos(
655 ErrorVariant::CustomError {
656 message: "error: big one".to_owned(),
657 },
658 pos,
659 );
660
661 assert_eq!(
662 format!("{}", error),
663 vec![
664 " --> 2:2",
665 " |",
666 "2 | cd",
667 " | ^---",
668 " |",
669 " = error: big one",
670 ]
671 .join("\n")
672 );
673 }
674
675 #[test]
display_custom_span_two_lines()676 fn display_custom_span_two_lines() {
677 let input = "ab\ncd\nefgh";
678 let start = position::Position::new(input, 4).unwrap();
679 let end = position::Position::new(input, 9).unwrap();
680 let error: Error<u32> = Error::new_from_span(
681 ErrorVariant::CustomError {
682 message: "error: big one".to_owned(),
683 },
684 start.span(&end),
685 );
686
687 assert_eq!(
688 format!("{}", error),
689 vec![
690 " --> 2:2",
691 " |",
692 "2 | cd",
693 "3 | efgh",
694 " | ^^",
695 " |",
696 " = error: big one",
697 ]
698 .join("\n")
699 );
700 }
701
702 #[test]
display_custom_span_three_lines()703 fn display_custom_span_three_lines() {
704 let input = "ab\ncd\nefgh";
705 let start = position::Position::new(input, 1).unwrap();
706 let end = position::Position::new(input, 9).unwrap();
707 let error: Error<u32> = Error::new_from_span(
708 ErrorVariant::CustomError {
709 message: "error: big one".to_owned(),
710 },
711 start.span(&end),
712 );
713
714 assert_eq!(
715 format!("{}", error),
716 vec![
717 " --> 1:2",
718 " |",
719 "1 | ab",
720 " | ...",
721 "3 | efgh",
722 " | ^^",
723 " |",
724 " = error: big one",
725 ]
726 .join("\n")
727 );
728 }
729
730 #[test]
display_custom_span_two_lines_inverted_cols()731 fn display_custom_span_two_lines_inverted_cols() {
732 let input = "abcdef\ngh";
733 let start = position::Position::new(input, 5).unwrap();
734 let end = position::Position::new(input, 8).unwrap();
735 let error: Error<u32> = Error::new_from_span(
736 ErrorVariant::CustomError {
737 message: "error: big one".to_owned(),
738 },
739 start.span(&end),
740 );
741
742 assert_eq!(
743 format!("{}", error),
744 vec![
745 " --> 1:6",
746 " |",
747 "1 | abcdef",
748 "2 | gh",
749 " | ^----^",
750 " |",
751 " = error: big one",
752 ]
753 .join("\n")
754 );
755 }
756
757 #[test]
display_custom_span_end_after_newline()758 fn display_custom_span_end_after_newline() {
759 let input = "abcdef\n";
760 let start = position::Position::new(input, 0).unwrap();
761 let end = position::Position::new(input, 7).unwrap();
762 assert!(start.at_start());
763 assert!(end.at_end());
764
765 let error: Error<u32> = Error::new_from_span(
766 ErrorVariant::CustomError {
767 message: "error: big one".to_owned(),
768 },
769 start.span(&end),
770 );
771
772 assert_eq!(
773 format!("{}", error),
774 vec![
775 " --> 1:1",
776 " |",
777 "1 | abcdef␊",
778 " | ^-----^",
779 " |",
780 " = error: big one",
781 ]
782 .join("\n")
783 );
784 }
785
786 #[test]
display_custom_span_empty()787 fn display_custom_span_empty() {
788 let input = "";
789 let start = position::Position::new(input, 0).unwrap();
790 let end = position::Position::new(input, 0).unwrap();
791 assert!(start.at_start());
792 assert!(end.at_end());
793
794 let error: Error<u32> = Error::new_from_span(
795 ErrorVariant::CustomError {
796 message: "error: empty".to_owned(),
797 },
798 start.span(&end),
799 );
800
801 assert_eq!(
802 format!("{}", error),
803 vec![
804 " --> 1:1",
805 " |",
806 "1 | ",
807 " | ^",
808 " |",
809 " = error: empty",
810 ]
811 .join("\n")
812 );
813 }
814
815 #[test]
mapped_parsing_error()816 fn mapped_parsing_error() {
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![1, 2, 3],
822 negatives: vec![4, 5, 6],
823 },
824 pos,
825 )
826 .renamed_rules(|n| format!("{}", n + 1));
827
828 assert_eq!(
829 format!("{}", error),
830 vec![
831 " --> 2:2",
832 " |",
833 "2 | cd",
834 " | ^---",
835 " |",
836 " = unexpected 5, 6, or 7; expected 2, 3, or 4",
837 ]
838 .join("\n")
839 );
840 }
841
842 #[test]
error_with_path()843 fn error_with_path() {
844 let input = "ab\ncd\nef";
845 let pos = position::Position::new(input, 4).unwrap();
846 let error: Error<u32> = Error::new_from_pos(
847 ErrorVariant::ParsingError {
848 positives: vec![1, 2, 3],
849 negatives: vec![4, 5, 6],
850 },
851 pos,
852 )
853 .with_path("file.rs");
854
855 assert_eq!(
856 format!("{}", error),
857 vec![
858 " --> file.rs:2:2",
859 " |",
860 "2 | cd",
861 " | ^---",
862 " |",
863 " = unexpected 4, 5, or 6; expected 1, 2, or 3",
864 ]
865 .join("\n")
866 );
867 }
868
869 #[test]
underline_with_tabs()870 fn underline_with_tabs() {
871 let input = "a\txbc";
872 let pos = position::Position::new(input, 2).unwrap();
873 let error: Error<u32> = Error::new_from_pos(
874 ErrorVariant::ParsingError {
875 positives: vec![1, 2, 3],
876 negatives: vec![4, 5, 6],
877 },
878 pos,
879 )
880 .with_path("file.rs");
881
882 assert_eq!(
883 format!("{}", error),
884 vec![
885 " --> file.rs:1:3",
886 " |",
887 "1 | a xbc",
888 " | ^---",
889 " |",
890 " = unexpected 4, 5, or 6; expected 1, 2, or 3",
891 ]
892 .join("\n")
893 );
894 }
895 }
896