Skip to main content

efmt_core/
parse.rs

1use crate::items::tokens::LexicalToken;
2use crate::span::{Position, Span as _};
3use std::path::PathBuf;
4use std::sync::Arc;
5
6pub use self::token_stream::TokenStream;
7pub use self::tokenizer::{TokenOrShebang, Tokenizer};
8
9/// A procedural macro to derive [Parse].
10pub use efmt_derive::Parse;
11
12pub(crate) mod token_stream;
13pub(crate) mod tokenizer;
14
15/// Possible errors.
16#[derive(Debug, Clone)]
17pub enum Error {
18    /// Unexpected EOF.
19    UnexpectedEof {
20        position: Position,
21        text: Arc<String>,
22        path: Option<Arc<PathBuf>>,
23    },
24
25    /// Unexpected token.
26    UnexpectedToken {
27        position: Position,
28        text: Arc<String>,
29        path: Option<Arc<PathBuf>>,
30    },
31
32    /// Error during tokenization.
33    TokenizeError {
34        source: erl_tokenize::Error,
35        text: Arc<String>,
36    },
37}
38
39impl Error {
40    pub(crate) fn unexpected_token(ts: &TokenStream, token: LexicalToken) -> Self {
41        Self::UnexpectedToken {
42            position: token.start_position(),
43            text: ts.text(),
44            path: ts.filepath(),
45        }
46    }
47
48    pub(crate) fn unexpected_eof(ts: &TokenStream) -> Self {
49        Self::UnexpectedEof {
50            position: ts.prev_token_end_position(),
51            text: ts.text(),
52            path: ts.filepath(),
53        }
54    }
55
56    pub(crate) fn tokenize_error(ts: &TokenStream, source: erl_tokenize::Error) -> Self {
57        Self::TokenizeError {
58            source,
59            text: ts.text(),
60        }
61    }
62
63    pub(crate) fn position(&self) -> Position {
64        match self {
65            Self::UnexpectedEof { position, .. } => *position,
66            Self::UnexpectedToken { position, .. } => *position,
67            Self::TokenizeError { source, .. } => source.position().clone().into(),
68        }
69    }
70
71    fn tokenize_error_message(source: &erl_tokenize::Error, text: &Arc<String>) -> String {
72        let source_message = source.to_string();
73        let source_message_end = source_message.find(" (").unwrap_or(source_message.len());
74        crate::error::generate_error_message(
75            text,
76            source.position().filepath(),
77            source.position().clone().into(),
78            &source_message[..source_message_end],
79        )
80    }
81
82    fn unexpected_eof_message(
83        position: &Position,
84        text: &Arc<String>,
85        path: &Option<Arc<PathBuf>>,
86    ) -> String {
87        crate::error::generate_error_message(
88            text,
89            path.as_ref().map(|x| &**x),
90            *position,
91            "unexpected EOF",
92        )
93    }
94
95    fn unexpected_token_message(
96        position: &Position,
97        text: &Arc<String>,
98        path: &Option<Arc<PathBuf>>,
99    ) -> String {
100        crate::error::generate_error_message(
101            text,
102            path.as_ref().map(|x| &**x),
103            *position,
104            "unexpected token",
105        )
106    }
107}
108
109impl std::fmt::Display for Error {
110    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111        match self {
112            Error::UnexpectedEof {
113                position,
114                text,
115                path,
116            } => {
117                write!(
118                    f,
119                    "Parse failed:{}",
120                    Self::unexpected_eof_message(position, text, path)
121                )
122            }
123            Error::UnexpectedToken {
124                position,
125                text,
126                path,
127            } => {
128                write!(
129                    f,
130                    "Parse failed:{}",
131                    Self::unexpected_token_message(position, text, path)
132                )
133            }
134            Error::TokenizeError { source, text } => {
135                write!(
136                    f,
137                    "Tokenize failed:{}",
138                    Self::tokenize_error_message(source, text)
139                )
140            }
141        }
142    }
143}
144
145impl std::error::Error for Error {
146    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
147        match self {
148            Error::UnexpectedEof { .. } | Error::UnexpectedToken { .. } => None,
149            Error::TokenizeError { source, .. } => Some(source),
150        }
151    }
152}
153
154/// A specialized [Result][std::result::Result] type for this module.
155pub type Result<T> = std::result::Result<T, Error>;
156
157/// This trait allows parsing an item from a token stream.
158pub trait Parse: Sized {
159    /// Parse an item from the given token stream.
160    fn parse(ts: &mut TokenStream) -> Result<Self>;
161}
162
163impl<T: Parse> Parse for Box<T> {
164    fn parse(ts: &mut TokenStream) -> Result<Self> {
165        ts.parse().map(Box::new)
166    }
167}
168
169impl<A: Parse, B: Parse> Parse for (A, B) {
170    fn parse(ts: &mut TokenStream) -> Result<Self> {
171        Ok((ts.parse()?, ts.parse()?))
172    }
173}
174
175impl<A: Parse, B: Parse, C: Parse> Parse for (A, B, C) {
176    fn parse(ts: &mut TokenStream) -> Result<Self> {
177        Ok((ts.parse()?, ts.parse()?, ts.parse()?))
178    }
179}
180
181/// This trait allows resuming to parse an item from a token stream.
182pub trait ResumeParse<A>: Parse {
183    /// Resume to parse an item from the given token stream.
184    ///
185    /// The second argument is the item that has already been parsed from the stream.
186    fn resume_parse(ts: &mut TokenStream, args: A) -> Result<Self>;
187}
188
189impl<T, A> ResumeParse<A> for Box<T>
190where
191    T: ResumeParse<A>,
192{
193    fn resume_parse(ts: &mut TokenStream, args: A) -> Result<Self> {
194        ts.resume_parse(args).map(Box::new)
195    }
196}
197
198#[cfg(test)]
199mod tests {
200    use crate::items::Module;
201
202    #[test]
203    fn unexpected_token_message_works() {
204        let text = indoc::indoc! {"
205        foo() ->
206            [a, b | #c].
207        "};
208        let err = crate::format_text::<Module>(text).err().unwrap();
209        similar_asserts::assert_eq!(
210            err.to_string(),
211            indoc::indoc! {"
212        Parse failed:
213        --> <unknown>:2:15
214        2 |     [a, b | #c].
215          |               ^ unexpected token"}
216        );
217    }
218
219    #[test]
220    fn unexpected_token_message_with_macro_works() {
221        let text = indoc::indoc! {"
222        -define(ID(A), A).
223        foo() ->
224            ?ID([a, b | #c]).
225        "};
226        let err = crate::format_text::<Module>(text).err().unwrap();
227        similar_asserts::assert_eq!(
228            err.to_string(),
229            indoc::indoc! {"
230        Parse failed:
231        --> <unknown>:3:19
232        3 |     ?ID([a, b | #c]).
233          |                   ^ unexpected token"}
234        );
235    }
236
237    #[test]
238    fn unexpected_eof_message_works() {
239        let text = indoc::indoc! {"
240        foo() ->
241            hello
242        "};
243        let err = crate::format_text::<Module>(text).err().unwrap();
244        similar_asserts::assert_eq!(
245            err.to_string(),
246            indoc::indoc! {"
247        Parse failed:
248        --> <unknown>:2:10
249        2 |     hello
250          |          ^ unexpected EOF"}
251        );
252    }
253
254    #[test]
255    fn tokenize_error_message_works() {
256        let text = indoc::indoc! {r#"
257        foo() ->
258            "hello
259        "#};
260        let err = crate::format_text::<Module>(text).err().unwrap();
261        similar_asserts::assert_eq!(
262            err.to_string(),
263            indoc::indoc! {r#"
264        Tokenize failed:
265        --> <unknown>:2:5
266        2 |     "hello
267          |     ^ no closing quotation"#}
268        );
269    }
270}