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
9pub use efmt_derive::Parse;
11
12pub(crate) mod token_stream;
13pub(crate) mod tokenizer;
14
15#[derive(Debug, Clone)]
17pub enum Error {
18 UnexpectedEof {
20 position: Position,
21 text: Arc<String>,
22 path: Option<Arc<PathBuf>>,
23 },
24
25 UnexpectedToken {
27 position: Position,
28 text: Arc<String>,
29 path: Option<Arc<PathBuf>>,
30 },
31
32 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
154pub type Result<T> = std::result::Result<T, Error>;
156
157pub trait Parse: Sized {
159 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
181pub trait ResumeParse<A>: Parse {
183 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}