]> git.proxmox.com Git - rustc.git/blame - vendor/pest/src/error.rs
New upstream version 1.76.0+dfsg1
[rustc.git] / vendor / pest / src / error.rs
CommitLineData
83c7162d
XL
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
9fa01778
XL
10//! Types for different kinds of parsing failures.
11
f2b60f7d
FG
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;
9fa01778
XL
25
26/// Parse-related error type.
27#[derive(Clone, Debug, Eq, Hash, PartialEq)]
f2b60f7d 28#[cfg_attr(feature = "std", derive(thiserror::Error))]
9fa01778
XL
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}
83c7162d 40
9fa01778 41/// Different kinds of parsing errors.
83c7162d 42#[derive(Clone, Debug, Eq, Hash, PartialEq)]
f2b60f7d 43#[cfg_attr(feature = "std", derive(thiserror::Error))]
9fa01778
XL
44pub enum ErrorVariant<R> {
45 /// Generated parsing error with expected and unexpected `Rule`s
83c7162d
XL
46 ParsingError {
47 /// Positive attempts
48 positives: Vec<R>,
49 /// Negative attempts
50 negatives: Vec<R>,
83c7162d 51 },
9fa01778
XL
52 /// Custom error with a message
53 CustomError {
83c7162d
XL
54 /// Short explanation
55 message: String,
83c7162d 56 },
83c7162d
XL
57}
58
9fa01778
XL
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
fe692bf9
FG
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, end) = value.split();
86 Self::Span(start.line_col(), end.line_col())
87 }
88}
89
9fa01778
XL
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 /// ```
9c376795 117 pub fn new_from_pos(variant: ErrorVariant<R>, pos: Position<'_>) -> Error<R> {
f2b60f7d
FG
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 };
9fa01778
XL
125 Error {
126 variant,
127 location: InputLocation::Pos(pos.pos()),
128 path: None,
f2b60f7d 129 line,
9fa01778
XL
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 /// ```
9c376795 163 pub fn new_from_span(variant: ErrorVariant<R>, span: Span<'_>) -> Error<R> {
9fa01778 164 let end = span.end_pos();
9fa01778
XL
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 {
f2b60f7d 168 let mut visual_end = end;
9fa01778
XL
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();
f2b60f7d
FG
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 {
9c376795 186 ll.map(str::to_owned)
f2b60f7d
FG
187 } else {
188 ll.map(visualize_whitespace)
189 };
9fa01778
XL
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
f2b60f7d
FG
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
9fa01778
XL
265 /// Renames all `Rule`s if this is a [`ParsingError`]. It does nothing when called on a
266 /// [`CustomError`].
83c7162d
XL
267 ///
268 /// Useful in order to rename verbose rules or have detailed per-`Rule` formatting.
269 ///
9fa01778
XL
270 /// [`ParsingError`]: enum.ErrorVariant.html#variant.ParsingError
271 /// [`CustomError`]: enum.ErrorVariant.html#variant.CustomError
272 ///
83c7162d
XL
273 /// # Examples
274 ///
275 /// ```
9fa01778 276 /// # use pest::error::{Error, ErrorVariant};
83c7162d
XL
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);
9fa01778
XL
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| {
83c7162d
XL
294 /// match *rule {
295 /// Rule::open_paren => "(".to_owned(),
296 /// Rule::closed_paren => "closed paren".to_owned()
297 /// }
298 /// });
299 /// ```
9fa01778 300 pub fn renamed_rules<F>(mut self, f: F) -> Error<R>
83c7162d 301 where
9fa01778 302 F: FnMut(&R) -> String,
83c7162d 303 {
9fa01778
XL
304 let variant = match self.variant {
305 ErrorVariant::ParsingError {
83c7162d
XL
306 positives,
307 negatives,
83c7162d 308 } => {
9fa01778
XL
309 let message = Error::parsing_error_message(&positives, &negatives, f);
310 ErrorVariant::CustomError { message }
83c7162d 311 }
9fa01778
XL
312 variant => variant,
313 };
83c7162d 314
9fa01778 315 self.variant = variant;
83c7162d 316
9fa01778 317 self
83c7162d 318 }
83c7162d 319
9fa01778
XL
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,
83c7162d
XL
324 }
325 }
83c7162d 326
9fa01778
XL
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 };
83c7162d 332
9fa01778 333 let line_str_len = format!("{}", line).len();
83c7162d 334
9fa01778
XL
335 let mut spacing = String::new();
336 for _ in 0..line_str_len {
337 spacing.push(' ');
83c7162d 338 }
83c7162d 339
9fa01778 340 spacing
83c7162d
XL
341 }
342
9fa01778
XL
343 fn underline(&self) -> String {
344 let mut underline = String::new();
83c7162d 345
9fa01778
XL
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 }
83c7162d 355
9fa01778
XL
356 Some(end)
357 }
358 _ => None,
359 };
360 let offset = start - 1;
3dfed10e 361 let line_chars = self.line.chars();
83c7162d 362
3dfed10e
XL
363 for c in line_chars.take(offset) {
364 match c {
365 '\t' => underline.push('\t'),
366 _ => underline.push(' '),
367 }
83c7162d
XL
368 }
369
9fa01778 370 if let Some(end) = end {
f2b60f7d 371 underline.push('^');
9fa01778 372 if end - start > 1 {
9fa01778
XL
373 for _ in 2..(end - start) {
374 underline.push('-');
375 }
376 underline.push('^');
83c7162d 377 }
9fa01778
XL
378 } else {
379 underline.push_str("^---")
380 }
83c7162d 381
9fa01778
XL
382 underline
383 }
83c7162d 384
9fa01778 385 fn message(&self) -> String {
f2b60f7d 386 self.variant.message().to_string()
83c7162d
XL
387 }
388
9fa01778
XL
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 }
83c7162d 404
9fa01778
XL
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 => {
f2b60f7d 413 let non_separated = f(&rules[l - 1]);
9fa01778
XL
414 let separated = rules
415 .iter()
416 .take(l - 1)
f2b60f7d 417 .map(f)
9fa01778
XL
418 .collect::<Vec<_>>()
419 .join(", ");
f2b60f7d 420 format!("{}, or {}", separated, non_separated)
9fa01778
XL
421 }
422 }
423 }
83c7162d 424
9fa01778
XL
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);
fe692bf9 434 if let (LineColLocation::Span(_, end), Some(ref continued_line)) = pair {
9fa01778
XL
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 }
83c7162d
XL
496}
497
f2b60f7d
FG
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());
9c376795 521 pub fn message(&self) -> Cow<'_, str> {
f2b60f7d
FG
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
9fa01778 534impl<R: RuleType> fmt::Display for Error<R> {
9c376795 535 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
9fa01778 536 write!(f, "{}", self.format())
83c7162d
XL
537 }
538}
539
f2b60f7d 540impl<R: RuleType> fmt::Display for ErrorVariant<R> {
9c376795 541 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f2b60f7d
FG
542 match self {
543 ErrorVariant::ParsingError { .. } => write!(f, "parsing error: {}", self.message()),
544 ErrorVariant::CustomError { .. } => write!(f, "{}", self.message()),
83c7162d
XL
545 }
546 }
547}
548
9fa01778
XL
549fn visualize_whitespace(input: &str) -> String {
550 input.to_owned().replace('\r', "␍").replace('\n', "␊")
551}
552
83c7162d
XL
553#[cfg(test)]
554mod tests {
83c7162d 555 use super::super::position;
9fa01778 556 use super::*;
f2b60f7d 557 use alloc::vec;
83c7162d
XL
558
559 #[test]
560 fn display_parsing_error_mixed() {
561 let input = "ab\ncd\nef";
9fa01778
XL
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 );
83c7162d
XL
570
571 assert_eq!(
572 format!("{}", error),
4b012472 573 [
83c7162d
XL
574 " --> 2:2",
575 " |",
f2b60f7d 576 "2 | cd",
83c7162d
XL
577 " | ^---",
578 " |",
4b012472 579 " = unexpected 4, 5, or 6; expected 1, 2, or 3"
9fa01778
XL
580 ]
581 .join("\n")
83c7162d
XL
582 );
583 }
584
585 #[test]
586 fn display_parsing_error_positives() {
587 let input = "ab\ncd\nef";
9fa01778
XL
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 );
83c7162d
XL
596
597 assert_eq!(
598 format!("{}", error),
4b012472 599 [
83c7162d
XL
600 " --> 2:2",
601 " |",
f2b60f7d 602 "2 | cd",
83c7162d
XL
603 " | ^---",
604 " |",
4b012472 605 " = expected 1 or 2"
9fa01778
XL
606 ]
607 .join("\n")
83c7162d
XL
608 );
609 }
610
611 #[test]
612 fn display_parsing_error_negatives() {
613 let input = "ab\ncd\nef";
9fa01778
XL
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 );
83c7162d
XL
622
623 assert_eq!(
624 format!("{}", error),
4b012472 625 [
83c7162d
XL
626 " --> 2:2",
627 " |",
f2b60f7d 628 "2 | cd",
83c7162d
XL
629 " | ^---",
630 " |",
4b012472 631 " = unexpected 4, 5, or 6"
9fa01778
XL
632 ]
633 .join("\n")
83c7162d
XL
634 );
635 }
636
637 #[test]
638 fn display_parsing_error_unknown() {
639 let input = "ab\ncd\nef";
9fa01778
XL
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 );
83c7162d
XL
648
649 assert_eq!(
650 format!("{}", error),
4b012472 651 [
83c7162d
XL
652 " --> 2:2",
653 " |",
f2b60f7d 654 "2 | cd",
83c7162d
XL
655 " | ^---",
656 " |",
4b012472 657 " = unknown parsing error"
9fa01778
XL
658 ]
659 .join("\n")
83c7162d
XL
660 );
661 }
662
663 #[test]
664 fn display_custom_pos() {
665 let input = "ab\ncd\nef";
9fa01778
XL
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 );
83c7162d
XL
673
674 assert_eq!(
675 format!("{}", error),
4b012472 676 [
83c7162d
XL
677 " --> 2:2",
678 " |",
f2b60f7d 679 "2 | cd",
83c7162d
XL
680 " | ^---",
681 " |",
4b012472 682 " = error: big one"
9fa01778
XL
683 ]
684 .join("\n")
83c7162d
XL
685 );
686 }
687
688 #[test]
689 fn display_custom_span_two_lines() {
690 let input = "ab\ncd\nefgh";
9fa01778
XL
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 );
83c7162d
XL
699
700 assert_eq!(
701 format!("{}", error),
4b012472 702 [
83c7162d
XL
703 " --> 2:2",
704 " |",
f2b60f7d 705 "2 | cd",
83c7162d
XL
706 "3 | efgh",
707 " | ^^",
708 " |",
4b012472 709 " = error: big one"
9fa01778
XL
710 ]
711 .join("\n")
83c7162d
XL
712 );
713 }
714
715 #[test]
716 fn display_custom_span_three_lines() {
717 let input = "ab\ncd\nefgh";
9fa01778
XL
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 );
83c7162d
XL
726
727 assert_eq!(
728 format!("{}", error),
4b012472 729 [
83c7162d
XL
730 " --> 1:2",
731 " |",
f2b60f7d 732 "1 | ab",
83c7162d
XL
733 " | ...",
734 "3 | efgh",
735 " | ^^",
736 " |",
4b012472 737 " = error: big one"
9fa01778
XL
738 ]
739 .join("\n")
83c7162d
XL
740 );
741 }
742
743 #[test]
9fa01778 744 fn display_custom_span_two_lines_inverted_cols() {
83c7162d 745 let input = "abcdef\ngh";
9fa01778
XL
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 );
83c7162d
XL
754
755 assert_eq!(
756 format!("{}", error),
4b012472 757 [
83c7162d
XL
758 " --> 1:6",
759 " |",
f2b60f7d 760 "1 | abcdef",
83c7162d
XL
761 "2 | gh",
762 " | ^----^",
763 " |",
4b012472 764 " = error: big one"
9fa01778
XL
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),
4b012472 787 [
9fa01778
XL
788 " --> 1:1",
789 " |",
790 "1 | abcdef␊",
791 " | ^-----^",
792 " |",
4b012472 793 " = error: big one"
9fa01778
XL
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),
4b012472 816 [
9fa01778
XL
817 " --> 1:1",
818 " |",
819 "1 | ",
820 " | ^",
821 " |",
4b012472 822 " = error: empty"
9fa01778
XL
823 ]
824 .join("\n")
83c7162d
XL
825 );
826 }
827
828 #[test]
829 fn mapped_parsing_error() {
830 let input = "ab\ncd\nef";
9fa01778
XL
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));
83c7162d
XL
840
841 assert_eq!(
842 format!("{}", error),
4b012472 843 [
83c7162d
XL
844 " --> 2:2",
845 " |",
f2b60f7d 846 "2 | cd",
83c7162d
XL
847 " | ^---",
848 " |",
4b012472 849 " = unexpected 5, 6, or 7; expected 2, 3, or 4"
9fa01778
XL
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),
4b012472 870 [
9fa01778
XL
871 " --> file.rs:2:2",
872 " |",
f2b60f7d 873 "2 | cd",
9fa01778
XL
874 " | ^---",
875 " |",
4b012472 876 " = unexpected 4, 5, or 6; expected 1, 2, or 3"
9fa01778
XL
877 ]
878 .join("\n")
83c7162d
XL
879 );
880 }
3dfed10e
XL
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),
4b012472 897 [
3dfed10e
XL
898 " --> file.rs:1:3",
899 " |",
900 "1 | a xbc",
901 " | ^---",
902 " |",
4b012472 903 " = unexpected 4, 5, or 6; expected 1, 2, or 3"
3dfed10e
XL
904 ]
905 .join("\n")
906 );
907 }
fe692bf9
FG
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 }
83c7162d 930}