brush_parser/
error.rs

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