1pub use crate::span::SourceSpan;
2use core::fmt;
3use owo_colors::OwoColorize;
4
5use thiserror::Error;
6
7use crate::{
8 token::{StrTag, StrTagSuffix, Token},
9 types::ShortStr,
10};
11
12pub type Result<T, E = ParseError> = std::result::Result<T, E>;
13
14#[derive(Error, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
15pub enum TokenError {
16 #[error("invalid utf-8 sequence")]
17 LitStrNotUtf8,
18 #[error("unexpected character {0:?}")]
19 UnexpectedChar(char),
20 #[error("unexpected end of file, expected `*/`")]
21 MissingCommentTerminator,
22 #[error("unknown string tag `{0}`")]
23 UnknownStrTag(ShortStr),
24 #[error("unexpected end of the string, expected `\"`")]
25 MissingStringTerminator,
26 #[error("unexpected end of {tag} string, expected {suffix}")]
27 MissingTaggedStringTerminator { tag: StrTag, suffix: StrTagSuffix },
28}
29
30#[derive(Error, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
31pub enum EscStrError {
32 #[error("got truncated hex escape code")]
33 TruncatedHex,
34 #[error("got invalid hex escape code")]
35 InvalidHex,
36 #[error("got invalid unicode hex escape code")]
37 InvalidUnicodeHex,
38 #[error("the code point U+{0:x} is invalid")]
39 InvalidUnicodePoint(u32),
40 #[error("got invalid unicode escape, expected `{{`")]
41 InvalidUnicodeMissingStartBrace,
42 #[error("got invalid unicode escape, expected `}}`")]
43 InvalidUnicodeMissingEndBrace,
44 #[error("got invalid escape character {0:?}")]
45 InvalidEscapeChar(char),
46}
47
48#[derive(Error, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
49pub enum IncludeError {
50 #[error("invalid utf-8 sequence")]
51 NotUtf8,
52
53 #[error(transparent)]
54 Open(logix_vfs::Error),
55}
56
57#[derive(Error, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
58pub enum PathError {
59 #[error("expected an absolute path")]
60 NotAbsolute,
61
62 #[error("expected a relative path")]
63 NotRelative,
64
65 #[error("the specified path is empty")]
66 EmptyPath,
67
68 #[error("expected either a file or directory name")]
69 NotName,
70
71 #[error("the path contains the invalid character {0:?}")]
72 InvalidChar(char),
73
74 #[error("expected file name or absolute path")]
75 NotFullOrNameOnly,
76
77 #[error("expected relative path when joining")]
78 JoinAbsolute,
79}
80
81#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
82pub enum Wanted {
83 Token(Token<'static>),
84 Tokens(&'static [Token<'static>]),
85 LitStr,
86 FullPath,
87 RelPath,
88 ExecutablePath,
89 NameOnlyPath,
90 ValidPath,
91 LitNum(&'static str),
92 Ident,
93 ItemOrEnd,
94 ItemDelim,
95 Item,
96 ItemUnknown,
98}
99
100impl fmt::Display for Wanted {
101 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
102 match self {
103 Self::Token(token) => token.write_token_display_name(f),
104 Self::Tokens([token]) => token.write_token_display_name(f),
105 Self::Tokens([a, b]) => {
106 write!(f, "either ")?;
107 a.write_token_display_name(f)?;
108 write!(f, " or ")?;
109 b.write_token_display_name(f)
110 }
111 Self::Tokens(tokens) => {
112 let (first, tokens) = tokens.split_first().unwrap();
113 let (last, tokens) = tokens.split_last().unwrap();
114
115 write!(f, "one of ")?;
116 first.write_token_display_name(f)?;
117 for token in tokens {
118 write!(f, ", ")?;
119 token.write_token_display_name(f)?;
120 }
121 write!(f, ", or ")?;
122 last.write_token_display_name(f)
123 }
124 Self::LitStr => write!(f, "string"),
125 Self::FullPath => write!(f, "full path"),
126 Self::RelPath => write!(f, "relative path"),
127 Self::ExecutablePath => write!(f, "executable name or full path"),
128 Self::NameOnlyPath => write!(f, "file or directory name"),
129 Self::ValidPath => write!(f, "path"),
130 Self::LitNum(name) => write!(f, "{name}"),
131 Self::Ident => write!(f, "identifier"),
132 Self::ItemOrEnd => write!(f, "item or end"),
133 Self::ItemDelim => write!(f, "delimiter"),
134 Self::Item => write!(f, "item"),
135 Self::ItemUnknown => write!(f, "item, delimiter or end"),
136 }
137 }
138}
139
140#[derive(Error, PartialEq, Eq, PartialOrd, Ord, Hash)]
141pub enum ParseError {
142 #[error(transparent)]
143 FsError(#[from] logix_vfs::Error),
144
145 #[error(transparent)]
146 Warning(Warn),
147
148 #[error("Missing struct member `{member}` while parsing `{type_name}` in {span}")]
149 MissingStructMember {
150 span: SourceSpan,
151 type_name: &'static str,
152 member: &'static str,
153 },
154
155 #[error("Duplicate struct member `{member}` while parsing `{type_name}` in {span}")]
156 DuplicateStructMember {
157 span: SourceSpan,
158 type_name: &'static str,
159 member: &'static str,
160 },
161
162 #[error("Unexpected {got_token} while parsing `{while_parsing}`, expected {wanted} in {span}")]
163 UnexpectedToken {
164 span: SourceSpan,
165 while_parsing: &'static str,
166 got_token: &'static str,
167 wanted: Wanted,
168 },
169
170 #[error("Failed to parse string, {error} in {span}")]
171 StrEscError {
172 span: SourceSpan,
173 error: EscStrError,
174 },
175
176 #[error("Failed to parse input, {error} in {span}")]
177 TokenError { span: SourceSpan, error: TokenError },
178
179 #[error("Failed to include file as `{while_parsing}`, {error} in {span}")]
180 IncludeError {
181 span: SourceSpan,
182 while_parsing: &'static str,
183 error: IncludeError,
184 },
185
186 #[error("Failed to parse path, {error} in {span}")]
187 PathError { span: SourceSpan, error: PathError },
188}
189
190impl fmt::Debug for ParseError {
191 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
192 writeln!(f)?;
193 match self {
194 Self::FsError(e) => writeln!(f, "{}{}", "error: ".bright_red().bold(), e.bold()),
195 Self::Warning(Warn::DuplicateMapEntry { span, key }) => write_error(
196 f,
197 format_args!("Duplicate entry `{key}` while parsing `Map`"),
198 span,
199 format_args!("overwrites the previous entry"),
200 ),
201 Self::MissingStructMember {
202 span,
203 type_name,
204 member,
205 } => write_error(
206 f,
207 format_args!("Missing struct member while parsing `{type_name}`"),
208 span,
209 format_args!("expected `{member}`"),
210 ),
211 Self::DuplicateStructMember {
212 span,
213 type_name,
214 member,
215 } => write_error(
216 f,
217 format_args!("Duplicate struct member while parsing `{type_name}`"),
218 span,
219 format_args!("unexpected `{member}`"),
220 ),
221 Self::UnexpectedToken {
222 span,
223 while_parsing,
224 got_token,
225 wanted,
226 } => write_error(
227 f,
228 format_args!("Unexpected {got_token} while parsing `{while_parsing}`"),
229 span,
230 format_args!("expected {wanted}"),
231 ),
232 Self::StrEscError { span, error } => {
233 write_error(f, "Failed to parse escaped string", span, error)
234 }
235 Self::TokenError { span, error } => {
236 write_error(f, "Failed to parse input", span, error)
237 }
238 Self::IncludeError {
239 span,
240 while_parsing,
241 error,
242 } => write_error(
243 f,
244 format_args!("Failed to include file as `{while_parsing}`"),
245 span,
246 error,
247 ),
248 Self::PathError { span, error } => write_error(f, "Failed to parse path", span, error),
249 }
250 }
251}
252
253fn write_error(
254 f: &mut impl fmt::Write,
255 message: impl fmt::Display,
256 span: &SourceSpan,
257 expected: impl fmt::Display,
258) -> fmt::Result {
259 let context = 1;
260 let ln_width = span.calc_ln_width(context);
261 writeln!(f, "{}{}", "error: ".bright_red().bold(), message.bold())?;
262
263 writeln!(
264 f,
265 " {} {}:{}:{}",
266 "--->".bright_blue().bold(),
267 span.path().display(),
268 span.line(),
269 span.col(),
270 )?;
271 writeln!(f, "{:>ln_width$} {}", "", "|".bright_blue().bold(),)?;
272
273 for (ln, span, line) in span.lines(context) {
274 writeln!(
275 f,
276 "{:>ln_width$} {} {}",
277 ln.bright_blue().bold(),
278 "|".bright_blue().bold(),
279 line.trim_end(),
280 )?;
281 if let Some(span) = span {
282 let col = span.start;
283 writeln!(
284 f,
285 "{:>ln_width$} {} {:>col$}{} {}",
286 "",
287 "|".bright_blue().bold(),
288 "",
289 "^".repeat(
290 line.get(span)
291 .into_iter()
292 .flat_map(|s| s.chars())
293 .count()
294 .max(1)
295 )
296 .bright_red()
297 .bold(),
298 expected.bright_red().bold(),
299 )?;
300 }
301 }
302
303 Ok(())
304}
305
306#[derive(Error, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
307pub enum Warn {
308 #[error(
309 "Duplicate entry `{key}` while parsing `Map`, overwrites the previous entry in {span}"
310 )]
311 DuplicateMapEntry { span: SourceSpan, key: ShortStr },
312}
313
314#[cfg(test)]
315mod tests {
316 use std::{error::Error, path::PathBuf};
317
318 use super::*;
319
320 #[test]
321 fn coverage_hacks() {
322 assert_eq!(
323 format!("{:?}", EscStrError::InvalidUnicodeHex),
324 "InvalidUnicodeHex",
325 );
326 assert_eq!(format!("{:?}", TokenError::LitStrNotUtf8), "LitStrNotUtf8",);
327 assert_eq!(format!("{:?}", Wanted::LitStr), "LitStr",);
328 assert_eq!(
329 format!(
330 "{:?}",
331 Warn::DuplicateMapEntry {
332 span: SourceSpan::empty(),
333 key: "test".into()
334 }
335 ),
336 "DuplicateMapEntry { span: SourceSpan { file: CachedFile(\"\"), pos: 0, range: SingleLine { line: 0, col: 0..0 } }, key: \"test\" }",
337 );
338 assert_eq!(
339 ParseError::FsError(logix_vfs::Error::NotFound {
340 path: PathBuf::new()
341 })
342 .source()
343 .map(|e| e.to_string()),
344 None,
345 );
346 }
347}