Skip to main content

shrub_parser/
error.rs

1use crate::tokenizer;
2
3/// Represents an error that occurred while parsing tokens.
4#[derive(thiserror::Error, Debug)]
5pub enum ParseError {
6    /// A parsing error occurred near the given position.
7    #[error("syntax error at line {} col {}", .0.line, .0.column)]
8    ParsingNear(crate::SourcePosition),
9
10    /// A parsing error occurred at the end of the input.
11    #[error("syntax error at end of input")]
12    ParsingAtEndOfInput,
13
14    /// An error occurred while tokenizing the input stream.
15    #[error("{} (detected near {})", .inner, .position.as_ref().map_or_else(|| String::from("<unknown position>"), |p| std::format!("line {} col {}", p.line, p.column)))]
16    Tokenizing {
17        /// The inner error.
18        inner: tokenizer::TokenizerError,
19        /// Optionally provides the position of the error.
20        position: Option<crate::SourcePosition>,
21    },
22}
23
24#[cfg(feature = "diagnostics")]
25#[allow(clippy::cast_sign_loss)]
26#[allow(unused)] // Workaround unused warnings in nightly versions of the compiler
27pub mod miette {
28    use miette::SourceOffset;
29
30    use super::ParseError;
31
32    impl ParseError {
33        /// Convert the original error to one miette can pretty print
34        pub fn to_pretty_error(self, input: impl Into<String>) -> PrettyError {
35            let input = input.into();
36            let location = match self {
37                Self::ParsingNear(ref pos) => {
38                    Some(SourceOffset::from_location(&input, pos.line, pos.column))
39                }
40                Self::Tokenizing { ref position, .. } => position
41                    .as_ref()
42                    .map(|p| SourceOffset::from_location(&input, p.line, p.column)),
43                Self::ParsingAtEndOfInput => {
44                    Some(SourceOffset::from_location(&input, usize::MAX, usize::MAX))
45                }
46            };
47
48            PrettyError {
49                cause: self,
50                input,
51                location,
52            }
53        }
54    }
55
56    /// Represents an error that occurred while parsing tokens.
57    #[derive(thiserror::Error, Debug, miette::Diagnostic)]
58    #[error("Cannot parse the input script")]
59    pub struct PrettyError {
60        cause: ParseError,
61        #[source_code]
62        input: String,
63        #[label("{cause}")]
64        location: Option<SourceOffset>,
65    }
66}
67
68/// Represents a parsing error with its location information
69#[derive(Debug, thiserror::Error)]
70#[error(transparent)]
71pub struct ParseErrorLocation {
72    #[from]
73    inner: peg::error::ParseError<peg::str::LineCol>,
74}
75
76/// Represents an error that occurred while parsing a word.
77#[derive(Debug, thiserror::Error)]
78pub enum WordParseError {
79    /// An error occurred while parsing an arithmetic expression.
80    #[error("failed to parse arithmetic expression")]
81    ArithmeticExpression(ParseErrorLocation),
82
83    /// An error occurred while parsing a shell pattern.
84    #[error("failed to parse pattern")]
85    Pattern(ParseErrorLocation),
86
87    /// An error occurred while parsing a prompt string.
88    #[error("failed to parse prompt string")]
89    Prompt(ParseErrorLocation),
90
91    /// An error occurred while parsing a parameter.
92    #[error("failed to parse parameter '{0}'")]
93    Parameter(String, ParseErrorLocation),
94
95    /// An error occurred while parsing for brace expansion.
96    #[error("failed to parse for brace expansion: '{0}'")]
97    BraceExpansion(String, ParseErrorLocation),
98
99    /// An error occurred while parsing a word.
100    #[error("failed to parse word '{0}'")]
101    Word(String, ParseErrorLocation),
102}
103
104/// Represents an error that occurred while parsing a (non-extended) test
105/// command.
106#[derive(Debug, thiserror::Error)]
107#[error(transparent)]
108pub struct TestCommandParseError(#[from] peg::error::ParseError<usize>);
109
110/// Represents an error that occurred while parsing a key-binding specification.
111#[derive(Debug, thiserror::Error)]
112pub enum BindingParseError {
113    /// An unknown error occurred while parsing a key-binding specification.
114    #[error("unknown error while parsing key-binding: '{0}'")]
115    Unknown(String),
116
117    /// A key code was missing from the key-binding specification.
118    #[error("missing key code in key-binding")]
119    MissingKeyCode,
120}
121
122pub(crate) fn convert_peg_parse_error(
123    err: &peg::error::ParseError<usize>,
124    tokens: &[crate::Token],
125) -> ParseError {
126    let approx_token_index = err.location;
127
128    if approx_token_index < tokens.len() {
129        let token = &tokens[approx_token_index];
130        ParseError::ParsingNear((*token.location().start).clone())
131    } else {
132        ParseError::ParsingAtEndOfInput
133    }
134}