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