Coverage Report

Created: 2026-04-29 06:53

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/pest-2.8.6/src/error.rs
Line
Count
Source
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 crate::parser_state::{ParseAttempts, ParsingToken, RulesCallStack};
13
use alloc::borrow::Cow;
14
use alloc::borrow::ToOwned;
15
use alloc::boxed::Box;
16
use alloc::collections::{BTreeMap, BTreeSet};
17
use alloc::format;
18
use alloc::string::String;
19
use alloc::string::ToString;
20
use alloc::vec;
21
use alloc::vec::Vec;
22
use core::cmp;
23
use core::fmt;
24
use core::mem;
25
26
use crate::position::Position;
27
use crate::span::Span;
28
use crate::RuleType;
29
30
/// Parse-related error type.
31
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
32
pub struct Error<R> {
33
    /// Variant of the error
34
    pub variant: ErrorVariant<R>,
35
    /// Location within the input string
36
    pub location: InputLocation,
37
    /// Line/column within the input string
38
    pub line_col: LineColLocation,
39
    inner: Box<ErrorInner<R>>,
40
}
41
42
/// Private information for parse errors.
43
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
44
struct ErrorInner<R> {
45
    path: Option<String>,
46
    line: String,
47
    continued_line: Option<String>,
48
    parse_attempts: Option<ParseAttempts<R>>,
49
}
50
51
impl<R: RuleType> core::error::Error for Error<R> {}
52
53
/// Different kinds of parsing errors.
54
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
55
pub enum ErrorVariant<R> {
56
    /// Generated parsing error with expected and unexpected `Rule`s
57
    ParsingError {
58
        /// Positive attempts
59
        positives: Vec<R>,
60
        /// Negative attempts
61
        negatives: Vec<R>,
62
    },
63
    /// Custom error with a message
64
    CustomError {
65
        /// Short explanation
66
        message: String,
67
    },
68
}
69
70
impl<R: RuleType> core::error::Error for ErrorVariant<R> {}
71
72
/// Where an `Error` has occurred.
73
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
74
pub enum InputLocation {
75
    /// `Error` was created by `Error::new_from_pos`
76
    Pos(usize),
77
    /// `Error` was created by `Error::new_from_span`
78
    Span((usize, usize)),
79
}
80
81
/// Line/column where an `Error` has occurred.
82
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
83
pub enum LineColLocation {
84
    /// Line/column pair if `Error` was created by `Error::new_from_pos`
85
    Pos((usize, usize)),
86
    /// Line/column pairs if `Error` was created by `Error::new_from_span`
87
    Span((usize, usize), (usize, usize)),
88
}
89
90
impl From<Position<'_>> for LineColLocation {
91
0
    fn from(value: Position<'_>) -> Self {
92
0
        Self::Pos(value.line_col())
93
0
    }
94
}
95
96
impl From<Span<'_>> for LineColLocation {
97
0
    fn from(value: Span<'_>) -> Self {
98
0
        let (start, end) = value.split();
99
0
        Self::Span(start.line_col(), end.line_col())
100
0
    }
101
}
102
103
/// Function mapping rule to its helper message defined by user.
104
pub type RuleToMessageFn<R> = Box<dyn Fn(&R) -> Option<String>>;
105
/// Function mapping string element to bool denoting whether it's a whitespace defined by user.
106
pub type IsWhitespaceFn = Box<dyn Fn(String) -> bool>;
107
108
impl ParsingToken {
109
0
    pub fn is_whitespace(&self, is_whitespace: &IsWhitespaceFn) -> bool {
110
0
        match self {
111
0
            ParsingToken::Sensitive { token } => is_whitespace(token.clone()),
112
0
            ParsingToken::Insensitive { token } => is_whitespace(token.clone()),
113
0
            ParsingToken::Range { .. } => false,
114
0
            ParsingToken::BuiltInRule => false,
115
        }
116
0
    }
117
}
118
119
impl<R: RuleType> ParseAttempts<R> {
120
    /// Helper formatting function to get message informing about tokens we've
121
    /// (un)expected to see.
122
    /// Used as a part of `parse_attempts_error`.
123
0
    fn tokens_helper_messages(
124
0
        &self,
125
0
        is_whitespace_fn: &IsWhitespaceFn,
126
0
        spacing: &str,
127
0
    ) -> Vec<String> {
128
0
        let mut helper_messages = Vec::new();
129
0
        let tokens_header_pairs = vec![
130
0
            (self.expected_tokens(), "expected"),
131
0
            (self.unexpected_tokens(), "unexpected"),
132
        ];
133
134
0
        for (tokens, header) in &tokens_header_pairs {
135
0
            if tokens.is_empty() {
136
0
                continue;
137
0
            }
138
139
0
            let mut helper_tokens_message = format!("{spacing}note: {header} ");
140
0
            helper_tokens_message.push_str(if tokens.len() == 1 {
141
0
                "token: "
142
            } else {
143
0
                "one of tokens: "
144
            });
145
146
0
            let expected_tokens_set: BTreeSet<String> = tokens
147
0
                .iter()
148
0
                .map(|token| {
149
0
                    if token.is_whitespace(is_whitespace_fn) {
150
0
                        String::from("WHITESPACE")
151
                    } else {
152
0
                        format!("`{}`", token)
153
                    }
154
0
                })
155
0
                .collect();
156
157
0
            helper_tokens_message.push_str(
158
0
                &expected_tokens_set
159
0
                    .iter()
160
0
                    .cloned()
161
0
                    .collect::<Vec<String>>()
162
0
                    .join(", "),
163
            );
164
0
            helper_messages.push(helper_tokens_message);
165
        }
166
167
0
        helper_messages
168
0
    }
169
}
170
171
impl<R: RuleType> Error<R> {
172
    /// Creates `Error` from `ErrorVariant` and `Position`.
173
    ///
174
    /// # Examples
175
    ///
176
    /// ```
177
    /// # use pest::error::{Error, ErrorVariant};
178
    /// # use pest::Position;
179
    /// # #[allow(non_camel_case_types)]
180
    /// # #[allow(dead_code)]
181
    /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
182
    /// # enum Rule {
183
    /// #     open_paren,
184
    /// #     closed_paren
185
    /// # }
186
    /// # let input = "";
187
    /// # let pos = Position::from_start(input);
188
    /// let error = Error::new_from_pos(
189
    ///     ErrorVariant::ParsingError {
190
    ///         positives: vec![Rule::open_paren],
191
    ///         negatives: vec![Rule::closed_paren],
192
    ///     },
193
    ///     pos
194
    /// );
195
    ///
196
    /// println!("{}", error);
197
    /// ```
198
0
    pub fn new_from_pos(variant: ErrorVariant<R>, pos: Position<'_>) -> Error<R> {
199
0
        let visualize_ws = pos.match_char('\n') || pos.match_char('\r');
200
0
        let line_of = pos.line_of();
201
0
        let line = if visualize_ws {
202
0
            visualize_whitespace(line_of)
203
        } else {
204
0
            line_of.replace(&['\r', '\n'][..], "")
205
        };
206
0
        Error {
207
0
            variant,
208
0
            location: InputLocation::Pos(pos.pos()),
209
0
            line_col: LineColLocation::Pos(pos.line_col()),
210
0
            inner: Box::new(ErrorInner {
211
0
                path: None,
212
0
                line,
213
0
                continued_line: None,
214
0
                parse_attempts: None,
215
0
            }),
216
0
        }
217
0
    }
Unexecuted instantiation: <pest::error::Error<dhall::syntax::text::parser::Rule>>::new_from_pos
Unexecuted instantiation: <pest::error::Error<_>>::new_from_pos
218
219
    /// Wrapper function to track `parse_attempts` as a result
220
    /// of `state` function call in `parser_state.rs`.
221
0
    pub(crate) fn new_from_pos_with_parsing_attempts(
222
0
        variant: ErrorVariant<R>,
223
0
        pos: Position<'_>,
224
0
        parse_attempts: ParseAttempts<R>,
225
0
    ) -> Error<R> {
226
0
        let mut error = Self::new_from_pos(variant, pos);
227
0
        error.inner.parse_attempts = Some(parse_attempts);
228
0
        error
229
0
    }
Unexecuted instantiation: <pest::error::Error<dhall::syntax::text::parser::Rule>>::new_from_pos_with_parsing_attempts
Unexecuted instantiation: <pest::error::Error<_>>::new_from_pos_with_parsing_attempts
230
231
    /// Creates `Error` from `ErrorVariant` and `Span`.
232
    ///
233
    /// # Examples
234
    ///
235
    /// ```
236
    /// # use pest::error::{Error, ErrorVariant};
237
    /// # use pest::{Position, Span};
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 start = Position::from_start(input);
247
    /// # let end = start.clone();
248
    /// # let span = start.span(&end);
249
    /// let error = Error::new_from_span(
250
    ///     ErrorVariant::ParsingError {
251
    ///         positives: vec![Rule::open_paren],
252
    ///         negatives: vec![Rule::closed_paren],
253
    ///     },
254
    ///     span
255
    /// );
256
    ///
257
    /// println!("{}", error);
258
    /// ```
259
0
    pub fn new_from_span(variant: ErrorVariant<R>, span: Span<'_>) -> Error<R> {
260
0
        let end = span.end_pos();
261
0
        let mut end_line_col = end.line_col();
262
        // end position is after a \n, so we want to point to the visual lf symbol
263
0
        if end_line_col.1 == 1 {
264
0
            let mut visual_end = end;
265
0
            visual_end.skip_back(1);
266
0
            let lc = visual_end.line_col();
267
0
            end_line_col = (lc.0, lc.1 + 1);
268
0
        };
269
270
0
        let mut line_iter = span.lines();
271
0
        let sl = line_iter.next().unwrap_or("");
272
0
        let mut chars = span.as_str().chars();
273
0
        let visualize_ws = matches!(chars.next(), Some('\n') | Some('\r'))
274
0
            || matches!(chars.last(), Some('\n') | Some('\r'));
275
0
        let start_line = if visualize_ws {
276
0
            visualize_whitespace(sl)
277
        } else {
278
0
            sl.to_owned().replace(&['\r', '\n'][..], "")
279
        };
280
0
        let ll = line_iter.last();
281
0
        let continued_line = if visualize_ws {
282
0
            ll.map(str::to_owned)
283
        } else {
284
0
            ll.map(visualize_whitespace)
285
        };
286
287
0
        Error {
288
0
            variant,
289
0
            location: InputLocation::Span((span.start(), end.pos())),
290
0
            line_col: LineColLocation::Span(span.start_pos().line_col(), end_line_col),
291
0
            inner: Box::new(ErrorInner {
292
0
                path: None,
293
0
                line: start_line,
294
0
                continued_line,
295
0
                parse_attempts: None,
296
0
            }),
297
0
        }
298
0
    }
Unexecuted instantiation: <pest::error::Error<dhall::syntax::text::parser::Rule>>::new_from_span
Unexecuted instantiation: <pest::error::Error<_>>::new_from_span
299
300
    /// Returns `Error` variant with `path` which is shown when formatted with `Display`.
301
    ///
302
    /// # Examples
303
    ///
304
    /// ```
305
    /// # use pest::error::{Error, ErrorVariant};
306
    /// # use pest::Position;
307
    /// # #[allow(non_camel_case_types)]
308
    /// # #[allow(dead_code)]
309
    /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
310
    /// # enum Rule {
311
    /// #     open_paren,
312
    /// #     closed_paren
313
    /// # }
314
    /// # let input = "";
315
    /// # let pos = Position::from_start(input);
316
    /// Error::new_from_pos(
317
    ///     ErrorVariant::ParsingError {
318
    ///         positives: vec![Rule::open_paren],
319
    ///         negatives: vec![Rule::closed_paren],
320
    ///     },
321
    ///     pos
322
    /// ).with_path("file.rs");
323
    /// ```
324
0
    pub fn with_path(mut self, path: &str) -> Error<R> {
325
0
        self.inner.path = Some(path.to_owned());
326
327
0
        self
328
0
    }
329
330
    /// Returns the path set using [`Error::with_path()`].
331
    ///
332
    /// # Examples
333
    ///
334
    /// ```
335
    /// # use pest::error::{Error, ErrorVariant};
336
    /// # use pest::Position;
337
    /// # #[allow(non_camel_case_types)]
338
    /// # #[allow(dead_code)]
339
    /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
340
    /// # enum Rule {
341
    /// #     open_paren,
342
    /// #     closed_paren
343
    /// # }
344
    /// # let input = "";
345
    /// # let pos = Position::from_start(input);
346
    /// # let error = Error::new_from_pos(
347
    /// #     ErrorVariant::ParsingError {
348
    /// #         positives: vec![Rule::open_paren],
349
    /// #         negatives: vec![Rule::closed_paren],
350
    /// #     },
351
    /// #     pos);
352
    /// let error = error.with_path("file.rs");
353
    /// assert_eq!(Some("file.rs"), error.path());
354
    /// ```
355
0
    pub fn path(&self) -> Option<&str> {
356
0
        self.inner.path.as_deref()
357
0
    }
358
359
    /// Returns the line that the error is on.
360
0
    pub fn line(&self) -> &str {
361
0
        self.inner.line.as_str()
362
0
    }
363
364
    /// Renames all `Rule`s if this is a [`ParsingError`]. It does nothing when called on a
365
    /// [`CustomError`].
366
    ///
367
    /// Useful in order to rename verbose rules or have detailed per-`Rule` formatting.
368
    ///
369
    /// [`ParsingError`]: enum.ErrorVariant.html#variant.ParsingError
370
    /// [`CustomError`]: enum.ErrorVariant.html#variant.CustomError
371
    ///
372
    /// # Examples
373
    ///
374
    /// ```
375
    /// # use pest::error::{Error, ErrorVariant};
376
    /// # use pest::Position;
377
    /// # #[allow(non_camel_case_types)]
378
    /// # #[allow(dead_code)]
379
    /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
380
    /// # enum Rule {
381
    /// #     open_paren,
382
    /// #     closed_paren
383
    /// # }
384
    /// # let input = "";
385
    /// # let pos = Position::from_start(input);
386
    /// Error::new_from_pos(
387
    ///     ErrorVariant::ParsingError {
388
    ///         positives: vec![Rule::open_paren],
389
    ///         negatives: vec![Rule::closed_paren],
390
    ///     },
391
    ///     pos
392
    /// ).renamed_rules(|rule| {
393
    ///     match *rule {
394
    ///         Rule::open_paren => "(".to_owned(),
395
    ///         Rule::closed_paren => "closed paren".to_owned()
396
    ///     }
397
    /// });
398
    /// ```
399
0
    pub fn renamed_rules<F>(mut self, f: F) -> Error<R>
400
0
    where
401
0
        F: FnMut(&R) -> String,
402
    {
403
0
        let variant = match self.variant {
404
            ErrorVariant::ParsingError {
405
0
                positives,
406
0
                negatives,
407
            } => {
408
0
                let message = Error::parsing_error_message(&positives, &negatives, f);
409
0
                ErrorVariant::CustomError { message }
410
            }
411
0
            variant => variant,
412
        };
413
414
0
        self.variant = variant;
415
416
0
        self
417
0
    }
418
419
    /// Get detailed information about errored rules sequence.
420
    /// Returns `Some(results)` only for `ParsingError`.
421
0
    pub fn parse_attempts(&self) -> Option<ParseAttempts<R>> {
422
0
        self.inner.parse_attempts.clone()
423
0
    }
424
425
    /// Get error message based on parsing attempts.
426
    /// Returns `None` in case self `parse_attempts` is `None`.
427
0
    pub fn parse_attempts_error(
428
0
        &self,
429
0
        input: &str,
430
0
        rule_to_message: &RuleToMessageFn<R>,
431
0
        is_whitespace: &IsWhitespaceFn,
432
0
    ) -> Option<Error<R>> {
433
0
        let attempts = if let Some(ref parse_attempts) = self.inner.parse_attempts {
434
0
            parse_attempts.clone()
435
        } else {
436
0
            return None;
437
        };
438
439
0
        let spacing = self.spacing() + "   ";
440
0
        let error_position = attempts.max_position;
441
0
        let message = {
442
0
            let mut help_lines: Vec<String> = Vec::new();
443
0
            help_lines.push(String::from("error: parsing error occurred."));
444
445
            // Note: at least one of `(un)expected_tokens` must not be empty.
446
0
            for tokens_helper_message in attempts.tokens_helper_messages(is_whitespace, &spacing) {
447
0
                help_lines.push(tokens_helper_message);
448
0
            }
449
450
0
            let call_stacks = attempts.call_stacks();
451
            // Group call stacks by their parents so that we can print common header and
452
            // several sub helper messages.
453
0
            let mut call_stacks_parents_groups: BTreeMap<Option<R>, Vec<RulesCallStack<R>>> =
454
0
                BTreeMap::new();
455
0
            for call_stack in call_stacks {
456
0
                call_stacks_parents_groups
457
0
                    .entry(call_stack.parent)
458
0
                    .or_default()
459
0
                    .push(call_stack);
460
0
            }
461
462
0
            for (group_parent, group) in call_stacks_parents_groups {
463
0
                if let Some(parent_rule) = group_parent {
464
0
                    let mut contains_meaningful_info = false;
465
0
                    help_lines.push(format!(
466
0
                        "{spacing}help: {}",
467
0
                        if let Some(message) = rule_to_message(&parent_rule) {
468
0
                            contains_meaningful_info = true;
469
0
                            message
470
                        } else {
471
0
                            String::from("[Unknown parent rule]")
472
                        }
473
                    ));
474
0
                    for call_stack in group {
475
0
                        if let Some(r) = call_stack.deepest.get_rule() {
476
0
                            if let Some(message) = rule_to_message(r) {
477
0
                                contains_meaningful_info = true;
478
0
                                help_lines.push(format!("{spacing}      - {message}"));
479
0
                            }
480
0
                        }
481
                    }
482
0
                    if !contains_meaningful_info {
483
0
                        // Have to remove useless line for unknown parent rule.
484
0
                        help_lines.pop();
485
0
                    }
486
                } else {
487
0
                    for call_stack in group {
488
                        // Note that `deepest` rule may be `None`. E.g. in case it corresponds
489
                        // to WHITESPACE expected token which has no parent rule (on the top level
490
                        // parsing).
491
0
                        if let Some(r) = call_stack.deepest.get_rule() {
492
0
                            let helper_message = rule_to_message(r);
493
0
                            if let Some(helper_message) = helper_message {
494
0
                                help_lines.push(format!("{spacing}help: {helper_message}"));
495
0
                            }
496
0
                        }
497
                    }
498
                }
499
            }
500
501
0
            help_lines.join("\n")
502
        };
503
0
        let error = Error::new_from_pos(
504
0
            ErrorVariant::CustomError { message },
505
0
            Position::new_internal(input, error_position),
506
        );
507
0
        Some(error)
508
0
    }
509
510
0
    fn start(&self) -> (usize, usize) {
511
0
        match self.line_col {
512
0
            LineColLocation::Pos(line_col) => line_col,
513
0
            LineColLocation::Span(start_line_col, _) => start_line_col,
514
        }
515
0
    }
Unexecuted instantiation: <pest::error::Error<dhall::syntax::text::parser::Rule>>::start
Unexecuted instantiation: <pest::error::Error<_>>::start
516
517
0
    fn spacing(&self) -> String {
518
0
        let line = match self.line_col {
519
0
            LineColLocation::Pos((line, _)) => line,
520
0
            LineColLocation::Span((start_line, _), (end_line, _)) => cmp::max(start_line, end_line),
521
        };
522
523
0
        let line_str_len = format!("{}", line).len();
524
525
0
        let mut spacing = String::new();
526
0
        for _ in 0..line_str_len {
527
0
            spacing.push(' ');
528
0
        }
529
530
0
        spacing
531
0
    }
Unexecuted instantiation: <pest::error::Error<dhall::syntax::text::parser::Rule>>::spacing
Unexecuted instantiation: <pest::error::Error<_>>::spacing
532
533
0
    fn underline(&self) -> String {
534
0
        let mut underline = String::new();
535
536
0
        let mut start = self.start().1;
537
0
        let end = match self.line_col {
538
0
            LineColLocation::Span(_, (_, mut end)) => {
539
0
                let inverted_cols = start > end;
540
0
                if inverted_cols {
541
0
                    mem::swap(&mut start, &mut end);
542
0
                    start -= 1;
543
0
                    end += 1;
544
0
                }
545
546
0
                Some(end)
547
            }
548
0
            _ => None,
549
        };
550
0
        let offset = start - 1;
551
0
        let line_chars = self.inner.line.chars();
552
553
0
        for c in line_chars.take(offset) {
554
0
            match c {
555
0
                '\t' => underline.push('\t'),
556
0
                _ => underline.push(' '),
557
            }
558
        }
559
560
0
        if let Some(end) = end {
561
0
            underline.push('^');
562
0
            if end - start > 1 {
563
0
                for _ in 2..(end - start) {
564
0
                    underline.push('-');
565
0
                }
566
0
                underline.push('^');
567
0
            }
568
        } else {
569
0
            underline.push_str("^---")
570
        }
571
572
0
        underline
573
0
    }
Unexecuted instantiation: <pest::error::Error<dhall::syntax::text::parser::Rule>>::underline
Unexecuted instantiation: <pest::error::Error<_>>::underline
574
575
0
    fn message(&self) -> String {
576
0
        self.variant.message().to_string()
577
0
    }
Unexecuted instantiation: <pest::error::Error<dhall::syntax::text::parser::Rule>>::message
Unexecuted instantiation: <pest::error::Error<_>>::message
578
579
0
    fn parsing_error_message<F>(positives: &[R], negatives: &[R], mut f: F) -> String
580
0
    where
581
0
        F: FnMut(&R) -> String,
582
    {
583
0
        match (negatives.is_empty(), positives.is_empty()) {
584
0
            (false, false) => format!(
585
0
                "unexpected {}; expected {}",
586
0
                Error::enumerate(negatives, &mut f),
587
0
                Error::enumerate(positives, &mut f)
588
            ),
589
0
            (false, true) => format!("unexpected {}", Error::enumerate(negatives, &mut f)),
590
0
            (true, false) => format!("expected {}", Error::enumerate(positives, &mut f)),
591
0
            (true, true) => "unknown parsing error".to_owned(),
592
        }
593
0
    }
Unexecuted instantiation: <pest::error::Error<dhall::syntax::text::parser::Rule>>::parsing_error_message::<<pest::error::ErrorVariant<dhall::syntax::text::parser::Rule>>::message::{closure#0}>
Unexecuted instantiation: <pest::error::Error<_>>::parsing_error_message::<_>
594
595
0
    fn enumerate<F>(rules: &[R], f: &mut F) -> String
596
0
    where
597
0
        F: FnMut(&R) -> String,
598
    {
599
0
        match rules.len() {
600
0
            1 => f(&rules[0]),
601
0
            2 => format!("{} or {}", f(&rules[0]), f(&rules[1])),
602
0
            l => {
603
0
                let non_separated = f(&rules[l - 1]);
604
0
                let separated = rules
605
0
                    .iter()
606
0
                    .take(l - 1)
607
0
                    .map(f)
608
0
                    .collect::<Vec<_>>()
609
0
                    .join(", ");
610
0
                format!("{}, or {}", separated, non_separated)
611
            }
612
        }
613
0
    }
Unexecuted instantiation: <pest::error::Error<dhall::syntax::text::parser::Rule>>::enumerate::<<pest::error::ErrorVariant<dhall::syntax::text::parser::Rule>>::message::{closure#0}>
Unexecuted instantiation: <pest::error::Error<_>>::enumerate::<_>
614
615
0
    pub(crate) fn format(&self) -> String {
616
0
        let spacing = self.spacing();
617
0
        let path = self
618
0
            .inner
619
0
            .path
620
0
            .as_ref()
621
0
            .map(|path| format!("{}:", path))
Unexecuted instantiation: <pest::error::Error<dhall::syntax::text::parser::Rule>>::format::{closure#0}
Unexecuted instantiation: <pest::error::Error<_>>::format::{closure#0}
622
0
            .unwrap_or_default();
623
624
0
        let pair = (self.line_col.clone(), &self.inner.continued_line);
625
0
        if let (LineColLocation::Span(_, end), Some(ref continued_line)) = pair {
626
0
            let has_line_gap = end.0 - self.start().0 > 1;
627
0
            if has_line_gap {
628
0
                format!(
629
0
                    "{s    }--> {p}{ls}:{c}\n\
630
0
                     {s    } |\n\
631
0
                     {ls:w$} | {line}\n\
632
0
                     {s    } | ...\n\
633
0
                     {le:w$} | {continued_line}\n\
634
0
                     {s    } | {underline}\n\
635
0
                     {s    } |\n\
636
0
                     {s    } = {message}",
637
                    s = spacing,
638
0
                    w = spacing.len(),
639
                    p = path,
640
0
                    ls = self.start().0,
641
                    le = end.0,
642
0
                    c = self.start().1,
643
                    line = self.inner.line,
644
                    continued_line = continued_line,
645
0
                    underline = self.underline(),
646
0
                    message = self.message()
647
                )
648
            } else {
649
0
                format!(
650
0
                    "{s    }--> {p}{ls}:{c}\n\
651
0
                     {s    } |\n\
652
0
                     {ls:w$} | {line}\n\
653
0
                     {le:w$} | {continued_line}\n\
654
0
                     {s    } | {underline}\n\
655
0
                     {s    } |\n\
656
0
                     {s    } = {message}",
657
                    s = spacing,
658
0
                    w = spacing.len(),
659
                    p = path,
660
0
                    ls = self.start().0,
661
                    le = end.0,
662
0
                    c = self.start().1,
663
                    line = self.inner.line,
664
                    continued_line = continued_line,
665
0
                    underline = self.underline(),
666
0
                    message = self.message()
667
                )
668
            }
669
        } else {
670
0
            format!(
671
0
                "{s}--> {p}{l}:{c}\n\
672
0
                 {s} |\n\
673
0
                 {l} | {line}\n\
674
0
                 {s} | {underline}\n\
675
0
                 {s} |\n\
676
0
                 {s} = {message}",
677
                s = spacing,
678
                p = path,
679
0
                l = self.start().0,
680
0
                c = self.start().1,
681
                line = self.inner.line,
682
0
                underline = self.underline(),
683
0
                message = self.message()
684
            )
685
        }
686
0
    }
Unexecuted instantiation: <pest::error::Error<dhall::syntax::text::parser::Rule>>::format
Unexecuted instantiation: <pest::error::Error<_>>::format
687
688
    #[cfg(feature = "miette-error")]
689
    /// Turns an error into a [miette](crates.io/miette) Diagnostic.
690
    pub fn into_miette(self) -> impl ::miette::Diagnostic {
691
        miette_adapter::MietteAdapter(self)
692
    }
693
}
694
695
impl<R: RuleType> ErrorVariant<R> {
696
    ///
697
    /// Returns the error message for [`ErrorVariant`]
698
    ///
699
    /// If [`ErrorVariant`] is [`CustomError`], it returns a
700
    /// [`Cow::Borrowed`] reference to [`message`]. If [`ErrorVariant`] is [`ParsingError`], a
701
    /// [`Cow::Owned`] containing "expected [ErrorVariant::ParsingError::positives] [ErrorVariant::ParsingError::negatives]" is returned.
702
    ///
703
    /// [`ErrorVariant`]: enum.ErrorVariant.html
704
    /// [`CustomError`]: enum.ErrorVariant.html#variant.CustomError
705
    /// [`ParsingError`]: enum.ErrorVariant.html#variant.ParsingError
706
    /// [`Cow::Owned`]: https://doc.rust-lang.org/std/borrow/enum.Cow.html#variant.Owned
707
    /// [`Cow::Borrowed`]: https://doc.rust-lang.org/std/borrow/enum.Cow.html#variant.Borrowed
708
    /// [`message`]: enum.ErrorVariant.html#variant.CustomError.field.message
709
    /// # Examples
710
    ///
711
    /// ```
712
    /// # use pest::error::ErrorVariant;
713
    /// let variant = ErrorVariant::<()>::CustomError {
714
    ///     message: String::from("unexpected error")
715
    /// };
716
    ///
717
    /// println!("{}", variant.message());
718
0
    pub fn message(&self) -> Cow<'_, str> {
719
0
        match self {
720
            ErrorVariant::ParsingError {
721
0
                ref positives,
722
0
                ref negatives,
723
0
            } => Cow::Owned(Error::parsing_error_message(positives, negatives, |r| {
724
0
                format!("{:?}", r)
725
0
            })),
Unexecuted instantiation: <pest::error::ErrorVariant<dhall::syntax::text::parser::Rule>>::message::{closure#0}
Unexecuted instantiation: <pest::error::ErrorVariant<_>>::message::{closure#0}
726
0
            ErrorVariant::CustomError { ref message } => Cow::Borrowed(message),
727
        }
728
0
    }
Unexecuted instantiation: <pest::error::ErrorVariant<dhall::syntax::text::parser::Rule>>::message
Unexecuted instantiation: <pest::error::ErrorVariant<_>>::message
729
}
730
731
impl<R: RuleType> fmt::Display for Error<R> {
732
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
733
0
        write!(f, "{}", self.format())
734
0
    }
Unexecuted instantiation: <pest::error::Error<dhall::syntax::text::parser::Rule> as core::fmt::Display>::fmt
Unexecuted instantiation: <pest::error::Error<_> as core::fmt::Display>::fmt
735
}
736
737
impl<R: RuleType> fmt::Display for ErrorVariant<R> {
738
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
739
0
        match self {
740
0
            ErrorVariant::ParsingError { .. } => write!(f, "parsing error: {}", self.message()),
741
0
            ErrorVariant::CustomError { .. } => write!(f, "{}", self.message()),
742
        }
743
0
    }
744
}
745
746
0
fn visualize_whitespace(input: &str) -> String {
747
0
    input.to_owned().replace('\r', "␍").replace('\n', "␊")
748
0
}
749
750
#[cfg(feature = "miette-error")]
751
mod miette_adapter {
752
    use alloc::string::ToString;
753
    use core::fmt;
754
    use std::boxed::Box;
755
756
    use crate::error::LineColLocation;
757
758
    use super::{Error, RuleType};
759
760
    use miette::{Diagnostic, LabeledSpan, SourceCode};
761
762
    #[derive(Debug)]
763
    pub(crate) struct MietteAdapter<R: RuleType>(pub(crate) Error<R>);
764
765
    impl<R: RuleType> Diagnostic for MietteAdapter<R> {
766
        fn source_code(&self) -> Option<&dyn SourceCode> {
767
            Some(&self.0.inner.line)
768
        }
769
770
        fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan>>> {
771
            let message = self.0.variant.message().to_string();
772
773
            let (offset, length) = match self.0.line_col {
774
                LineColLocation::Pos((_, c)) => (c - 1, 1),
775
                LineColLocation::Span((_, start_c), (_, end_c)) => {
776
                    (start_c - 1, end_c - start_c + 1)
777
                }
778
            };
779
780
            let span = LabeledSpan::new(Some(message), offset, length);
781
782
            Some(Box::new(std::iter::once(span)))
783
        }
784
785
        fn help<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
786
            Some(Box::new(self.0.message()))
787
        }
788
    }
789
790
    impl<R: RuleType> fmt::Display for MietteAdapter<R> {
791
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
792
            write!(f, "Failure to parse at {:?}", self.0.line_col)
793
        }
794
    }
795
796
    impl<R> core::error::Error for MietteAdapter<R>
797
    where
798
        R: RuleType,
799
        Self: fmt::Debug + fmt::Display,
800
    {
801
    }
802
}
803
804
#[cfg(test)]
805
mod tests {
806
    use super::*;
807
    use alloc::vec;
808
809
    #[test]
810
    fn display_parsing_error_mixed() {
811
        let input = "ab\ncd\nef";
812
        let pos = Position::new(input, 4).unwrap();
813
        let error: Error<u32> = Error::new_from_pos(
814
            ErrorVariant::ParsingError {
815
                positives: vec![1, 2, 3],
816
                negatives: vec![4, 5, 6],
817
            },
818
            pos,
819
        );
820
821
        assert_eq!(
822
            format!("{}", error),
823
            [
824
                " --> 2:2",
825
                "  |",
826
                "2 | cd",
827
                "  |  ^---",
828
                "  |",
829
                "  = unexpected 4, 5, or 6; expected 1, 2, or 3"
830
            ]
831
            .join("\n")
832
        );
833
    }
834
835
    #[test]
836
    fn display_parsing_error_positives() {
837
        let input = "ab\ncd\nef";
838
        let pos = Position::new(input, 4).unwrap();
839
        let error: Error<u32> = Error::new_from_pos(
840
            ErrorVariant::ParsingError {
841
                positives: vec![1, 2],
842
                negatives: vec![],
843
            },
844
            pos,
845
        );
846
847
        assert_eq!(
848
            format!("{}", error),
849
            [
850
                " --> 2:2",
851
                "  |",
852
                "2 | cd",
853
                "  |  ^---",
854
                "  |",
855
                "  = expected 1 or 2"
856
            ]
857
            .join("\n")
858
        );
859
    }
860
861
    #[test]
862
    fn display_parsing_error_negatives() {
863
        let input = "ab\ncd\nef";
864
        let pos = Position::new(input, 4).unwrap();
865
        let error: Error<u32> = Error::new_from_pos(
866
            ErrorVariant::ParsingError {
867
                positives: vec![],
868
                negatives: vec![4, 5, 6],
869
            },
870
            pos,
871
        );
872
873
        assert_eq!(
874
            format!("{}", error),
875
            [
876
                " --> 2:2",
877
                "  |",
878
                "2 | cd",
879
                "  |  ^---",
880
                "  |",
881
                "  = unexpected 4, 5, or 6"
882
            ]
883
            .join("\n")
884
        );
885
    }
886
887
    #[test]
888
    fn display_parsing_error_unknown() {
889
        let input = "ab\ncd\nef";
890
        let pos = Position::new(input, 4).unwrap();
891
        let error: Error<u32> = Error::new_from_pos(
892
            ErrorVariant::ParsingError {
893
                positives: vec![],
894
                negatives: vec![],
895
            },
896
            pos,
897
        );
898
899
        assert_eq!(
900
            format!("{}", error),
901
            [
902
                " --> 2:2",
903
                "  |",
904
                "2 | cd",
905
                "  |  ^---",
906
                "  |",
907
                "  = unknown parsing error"
908
            ]
909
            .join("\n")
910
        );
911
    }
912
913
    #[test]
914
    fn display_custom_pos() {
915
        let input = "ab\ncd\nef";
916
        let pos = Position::new(input, 4).unwrap();
917
        let error: Error<u32> = Error::new_from_pos(
918
            ErrorVariant::CustomError {
919
                message: "error: big one".to_owned(),
920
            },
921
            pos,
922
        );
923
924
        assert_eq!(
925
            format!("{}", error),
926
            [
927
                " --> 2:2",
928
                "  |",
929
                "2 | cd",
930
                "  |  ^---",
931
                "  |",
932
                "  = error: big one"
933
            ]
934
            .join("\n")
935
        );
936
    }
937
938
    #[test]
939
    fn display_custom_span_two_lines() {
940
        let input = "ab\ncd\nefgh";
941
        let start = Position::new(input, 4).unwrap();
942
        let end = Position::new(input, 9).unwrap();
943
        let error: Error<u32> = Error::new_from_span(
944
            ErrorVariant::CustomError {
945
                message: "error: big one".to_owned(),
946
            },
947
            start.span(&end),
948
        );
949
950
        assert_eq!(
951
            format!("{}", error),
952
            [
953
                " --> 2:2",
954
                "  |",
955
                "2 | cd",
956
                "3 | efgh",
957
                "  |  ^^",
958
                "  |",
959
                "  = error: big one"
960
            ]
961
            .join("\n")
962
        );
963
    }
964
965
    #[test]
966
    fn display_custom_span_three_lines() {
967
        let input = "ab\ncd\nefgh";
968
        let start = Position::new(input, 1).unwrap();
969
        let end = Position::new(input, 9).unwrap();
970
        let error: Error<u32> = Error::new_from_span(
971
            ErrorVariant::CustomError {
972
                message: "error: big one".to_owned(),
973
            },
974
            start.span(&end),
975
        );
976
977
        assert_eq!(
978
            format!("{}", error),
979
            [
980
                " --> 1:2",
981
                "  |",
982
                "1 | ab",
983
                "  | ...",
984
                "3 | efgh",
985
                "  |  ^^",
986
                "  |",
987
                "  = error: big one"
988
            ]
989
            .join("\n")
990
        );
991
    }
992
993
    #[test]
994
    fn display_custom_span_two_lines_inverted_cols() {
995
        let input = "abcdef\ngh";
996
        let start = Position::new(input, 5).unwrap();
997
        let end = Position::new(input, 8).unwrap();
998
        let error: Error<u32> = Error::new_from_span(
999
            ErrorVariant::CustomError {
1000
                message: "error: big one".to_owned(),
1001
            },
1002
            start.span(&end),
1003
        );
1004
1005
        assert_eq!(
1006
            format!("{}", error),
1007
            [
1008
                " --> 1:6",
1009
                "  |",
1010
                "1 | abcdef",
1011
                "2 | gh",
1012
                "  | ^----^",
1013
                "  |",
1014
                "  = error: big one"
1015
            ]
1016
            .join("\n")
1017
        );
1018
    }
1019
1020
    #[test]
1021
    fn display_custom_span_end_after_newline() {
1022
        let input = "abcdef\n";
1023
        let start = Position::new(input, 0).unwrap();
1024
        let end = Position::new(input, 7).unwrap();
1025
        assert!(start.at_start());
1026
        assert!(end.at_end());
1027
1028
        let error: Error<u32> = Error::new_from_span(
1029
            ErrorVariant::CustomError {
1030
                message: "error: big one".to_owned(),
1031
            },
1032
            start.span(&end),
1033
        );
1034
1035
        assert_eq!(
1036
            format!("{}", error),
1037
            [
1038
                " --> 1:1",
1039
                "  |",
1040
                "1 | abcdef␊",
1041
                "  | ^-----^",
1042
                "  |",
1043
                "  = error: big one"
1044
            ]
1045
            .join("\n")
1046
        );
1047
    }
1048
1049
    #[test]
1050
    fn display_custom_span_empty() {
1051
        let input = "";
1052
        let start = Position::new(input, 0).unwrap();
1053
        let end = Position::new(input, 0).unwrap();
1054
        assert!(start.at_start());
1055
        assert!(end.at_end());
1056
1057
        let error: Error<u32> = Error::new_from_span(
1058
            ErrorVariant::CustomError {
1059
                message: "error: empty".to_owned(),
1060
            },
1061
            start.span(&end),
1062
        );
1063
1064
        assert_eq!(
1065
            format!("{}", error),
1066
            [
1067
                " --> 1:1",
1068
                "  |",
1069
                "1 | ",
1070
                "  | ^",
1071
                "  |",
1072
                "  = error: empty"
1073
            ]
1074
            .join("\n")
1075
        );
1076
    }
1077
1078
    #[test]
1079
    fn mapped_parsing_error() {
1080
        let input = "ab\ncd\nef";
1081
        let pos = Position::new(input, 4).unwrap();
1082
        let error: Error<u32> = Error::new_from_pos(
1083
            ErrorVariant::ParsingError {
1084
                positives: vec![1, 2, 3],
1085
                negatives: vec![4, 5, 6],
1086
            },
1087
            pos,
1088
        )
1089
        .renamed_rules(|n| format!("{}", n + 1));
1090
1091
        assert_eq!(
1092
            format!("{}", error),
1093
            [
1094
                " --> 2:2",
1095
                "  |",
1096
                "2 | cd",
1097
                "  |  ^---",
1098
                "  |",
1099
                "  = unexpected 5, 6, or 7; expected 2, 3, or 4"
1100
            ]
1101
            .join("\n")
1102
        );
1103
    }
1104
1105
    #[test]
1106
    fn error_with_path() {
1107
        let input = "ab\ncd\nef";
1108
        let pos = Position::new(input, 4).unwrap();
1109
        let error: Error<u32> = Error::new_from_pos(
1110
            ErrorVariant::ParsingError {
1111
                positives: vec![1, 2, 3],
1112
                negatives: vec![4, 5, 6],
1113
            },
1114
            pos,
1115
        )
1116
        .with_path("file.rs");
1117
1118
        assert_eq!(
1119
            format!("{}", error),
1120
            [
1121
                " --> file.rs:2:2",
1122
                "  |",
1123
                "2 | cd",
1124
                "  |  ^---",
1125
                "  |",
1126
                "  = unexpected 4, 5, or 6; expected 1, 2, or 3"
1127
            ]
1128
            .join("\n")
1129
        );
1130
    }
1131
1132
    #[test]
1133
    fn underline_with_tabs() {
1134
        let input = "a\txbc";
1135
        let pos = Position::new(input, 2).unwrap();
1136
        let error: Error<u32> = Error::new_from_pos(
1137
            ErrorVariant::ParsingError {
1138
                positives: vec![1, 2, 3],
1139
                negatives: vec![4, 5, 6],
1140
            },
1141
            pos,
1142
        )
1143
        .with_path("file.rs");
1144
1145
        assert_eq!(
1146
            format!("{}", error),
1147
            [
1148
                " --> file.rs:1:3",
1149
                "  |",
1150
                "1 | a  xbc",
1151
                "  |    ^---",
1152
                "  |",
1153
                "  = unexpected 4, 5, or 6; expected 1, 2, or 3"
1154
            ]
1155
            .join("\n")
1156
        );
1157
    }
1158
1159
    #[test]
1160
    fn pos_to_lcl_conversion() {
1161
        let input = "input";
1162
1163
        let pos = Position::new(input, 2).unwrap();
1164
1165
        assert_eq!(LineColLocation::Pos(pos.line_col()), pos.into());
1166
    }
1167
1168
    #[test]
1169
    fn span_to_lcl_conversion() {
1170
        let input = "input";
1171
1172
        let span = Span::new(input, 2, 4).unwrap();
1173
        let (start, end) = span.split();
1174
1175
        assert_eq!(
1176
            LineColLocation::Span(start.line_col(), end.line_col()),
1177
            span.into()
1178
        );
1179
    }
1180
1181
    #[cfg(feature = "miette-error")]
1182
    #[test]
1183
    fn miette_error() {
1184
        let input = "abc\ndef";
1185
        let pos = Position::new(input, 4).unwrap();
1186
        let error: Error<u32> = Error::new_from_pos(
1187
            ErrorVariant::ParsingError {
1188
                positives: vec![1, 2, 3],
1189
                negatives: vec![4, 5, 6],
1190
            },
1191
            pos,
1192
        );
1193
1194
        let miette_error = miette::Error::new(error.into_miette());
1195
1196
        assert_eq!(
1197
            format!("{:?}", miette_error),
1198
            [
1199
                "  \u{1b}[31m×\u{1b}[0m Failure to parse at Pos((2, 1))",
1200
                "   ╭────",
1201
                " \u{1b}[2m1\u{1b}[0m │ def",
1202
                "   · \u{1b}[35;1m┬\u{1b}[0m",
1203
                "   · \u{1b}[35;1m╰── \u{1b}[35;1munexpected 4, 5, or 6; expected 1, 2, or 3\u{1b}[0m\u{1b}[0m",
1204
                "   ╰────",
1205
                "\u{1b}[36m  help: \u{1b}[0munexpected 4, 5, or 6; expected 1, 2, or 3\n"
1206
            ]
1207
            .join("\n")
1208
        );
1209
    }
1210
}