rustemo/
error.rs

1use crate::{location::Location, Context, Input, State};
2use std::fmt::{Debug, Display};
3
4pub type Result<R> = std::result::Result<R, Error>;
5
6/// Error type returned in `Err` variant of `Result` type from the parser.
7// ANCHOR: parser-error
8#[derive(Debug)]
9pub enum Error {
10    Error {
11        message: String,
12        file: Option<String>,
13        location: Option<Location>,
14    },
15    IOError(std::io::Error),
16}
17// ANCHOR_END: parser-error
18
19impl Error {
20    /// A string representation of the error without the full file path.
21    /// Used in tests to yield the same results at different location.
22    pub fn to_locfile_str(&self) -> String {
23        match self {
24            Error::Error {
25                message,
26                file,
27                location,
28            } => {
29                let mut loc_str = String::from("Error");
30                if file.is_some() || location.is_some() {
31                    loc_str.push_str(" at ");
32                }
33                if let Some(file) = file {
34                    if let Some((_, file)) = file.rsplit_once('/') {
35                        loc_str.push_str(file);
36                    } else {
37                        loc_str.push_str(file);
38                    }
39                    if location.is_some() {
40                        loc_str.push(':');
41                    }
42                }
43                if let Some(location) = location {
44                    loc_str.push_str(&format!("{location:?}"));
45                }
46                format!("{}:\n\t{}", loc_str, message.replace('\n', "\n\t"))
47            }
48            Error::IOError(e) => format!("IOError: {e}"),
49        }
50    }
51}
52
53impl Display for Error {
54    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55        match self {
56            Error::Error {
57                message,
58                file,
59                location,
60            } => {
61                let mut loc_str = String::from("Error");
62                if file.is_some() || location.is_some() {
63                    loc_str.push_str(" at ");
64                }
65                if let Some(file) = file {
66                    loc_str.push_str(file);
67                    if location.is_some() {
68                        loc_str.push(':');
69                    }
70                }
71                if let Some(location) = location {
72                    loc_str.push_str(&format!("{location:?}"));
73                }
74                write!(f, "{}:\n\t{}", loc_str, message.replace('\n', "\n\t"))
75            }
76            Error::IOError(e) => write!(f, "IOError: {e}"),
77        }
78    }
79}
80
81impl From<std::io::Error> for Error {
82    fn from(e: std::io::Error) -> Self {
83        Error::IOError(e)
84    }
85}
86
87impl<R> From<Error> for Result<R> {
88    fn from(value: Error) -> Self {
89        Self::Err(value)
90    }
91}
92
93pub(crate) fn error_expected<'i, I, S, TK, C>(
94    input: &'i I,
95    file_name: &str,
96    context: &C,
97    expected: &[TK],
98) -> Error
99where
100    C: Context<'i, I, S, TK>,
101    I: Input + ?Sized,
102    S: State,
103    TK: Debug,
104{
105    let expected = if expected.len() > 1 {
106        format!(
107            "one of {}",
108            expected
109                .iter()
110                .map(|t| format!("{t:?}"))
111                .collect::<Vec<_>>()
112                .join(", ")
113        )
114    } else {
115        format!("{:?}", expected[0])
116    };
117    Error::Error {
118        message: format!(
119            "...{}...\nExpected {}.",
120            input.context_str(context.position()),
121            expected
122        ),
123        file: Some(file_name.to_string()),
124        location: Some(context.location()),
125    }
126}
127
128/// Creates error Result from message, file and location
129#[macro_export]
130macro_rules! err {
131    ($message:expr) => {
132        Result::from(Error::Error {
133            message: $message,
134            file: None,
135            location: None,
136        })
137    };
138    ($message:expr, $file:expr) => {
139        Result::from(Error::Error {
140            message: $message,
141            file: $file,
142            location: None,
143        })
144    };
145    ($message:expr, $file:expr, $location:expr) => {
146        Result::from(Error::Error {
147            message: $message,
148            file: $file,
149            location: $location,
150        })
151    };
152}