1use super::Str;
2
3#[derive(Debug, PartialEq, Eq)]
5pub enum Error<'a> {
6 PositionalArgAfterKeywordArg {
12 positional_arg: Str<'a>,
13 keyword_arg: Str<'a>,
14 },
15
16 IncorrectType {
18 wanted_type: &'static str,
19 got_type: &'static str,
20 got_raw_value: Str<'a>,
21 function_name: Str<'a>,
22 parameter_name: &'a str,
23 },
24
25 TooManyPositionalArgs {
27 extra_positional_arg: Str<'a>,
28 function_name: Str<'a>,
29 max_positional_args: usize,
30 },
31
32 NoSuchArgument {
34 function_name: Str<'a>,
35 argument: Str<'a>,
36 },
37
38 DuplicateArgument {
40 parameter_name: &'a str,
41 first_assignment: Str<'a>,
42 second_assignment: Str<'a>,
43 },
44
45 NoSuchFunction { function_name: Str<'a> },
47
48 UnmatchedOpeningBracket { open: Str<'a> },
50
51 UnmatchedClosingBracket { close: Str<'a> },
53
54 InvalidDimensionUnit { dimension: Str<'a>, unit: Str<'a> },
56
57 UnexpectedToken {
59 want: &'static str,
60 got: Str<'a>,
61 skipped: Str<'a>,
62 },
63
64 MissingArgsForFunction { function_name: Str<'a> },
66
67 MismatchedBraces { open: Str<'a>, close: Str<'a> },
69
70 IncompleteKeywordArg { keyword: Str<'a> },
72
73 MultipleDecimalPoints { point: Str<'a> },
75
76 NumberWithoutUnits { number: Str<'a> },
78
79 InvalidCharacter { char: Str<'a> },
81}
82
83impl<'a> Error<'a> {
84 pub fn message(&self) -> String {
85 use Error::*;
86 match self {
87 PositionalArgAfterKeywordArg { .. } => {
88 "A positional argument appears after a keyword argument".into()
89 }
90 IncorrectType { .. } => "An argument has the wrong type".into(),
91 TooManyPositionalArgs { .. } => "Too many positional arguments provided".into(),
92 NoSuchArgument { .. } => "No such argument to this function".into(),
93 DuplicateArgument { parameter_name, .. } => {
94 format!["The `{parameter_name}` argument was provided multiple times"]
95 }
96 NoSuchFunction { function_name } => {
97 format!["Unknown function `{function_name}`"]
98 }
99 UnmatchedOpeningBracket { .. } => "Unmatched opening bracket".into(),
100 UnmatchedClosingBracket { .. } => "Unmatched closing bracket".into(),
101 InvalidDimensionUnit { dimension: _, unit } => {
102 format!["Dimension has an invalid unit '{unit}'"]
103 }
104 UnexpectedToken { want, .. } => {
105 format!["Unexpected token while looking for {want}"]
106 }
107 MissingArgsForFunction { function_name } => {
108 format!["No arguments provided for '{function_name}'"]
109 }
110 MismatchedBraces { open, close } => format!["Mismatched braces '{open}' and '{close}'"],
111 IncompleteKeywordArg { .. } => "Incomplete keyword argument".into(),
112 MultipleDecimalPoints { .. } => "A number has multiple decimal points".into(),
113 NumberWithoutUnits { .. } => "No units were provided for this number".into(),
114 InvalidCharacter { .. } => "Invalid character in the input".into(),
115 }
116 }
117 pub fn labels(&self) -> Vec<ErrorLabel> {
118 use Error::*;
119 match self {
120 PositionalArgAfterKeywordArg {
121 positional_arg,
122 keyword_arg,
123 } => {
124 vec![
125 ErrorLabel {
126 span: positional_arg.span(),
127 text: "The positional argument appears here".into(),
128 },
129 ErrorLabel {
130 span: keyword_arg.span(),
131 text: "The keyword argument appears here".into(),
132 },
133 ]
134 }
135 IncorrectType {
136 wanted_type,
137 got_type: got,
138 got_raw_value,
139 function_name,
140 parameter_name,
141 } => vec![
142 ErrorLabel {
143 span: got_raw_value.span(),
144 text: format!["The provided argument is {got}"],
145 },
146 ErrorLabel {
147 span: function_name.span(),
148 text: format![
149 "The `{parameter_name}` parameter of the `{}` function requires {wanted_type}",
150 function_name.str()
151 ],
152 },
153 ],
154 TooManyPositionalArgs { extra_positional_arg, function_name, max_positional_args } => vec![
155 ErrorLabel {
156 span: extra_positional_arg.span(),
157 text: "an extra positional arguments was provided".to_string(),
158 },
159 ErrorLabel {
160 span: function_name.span(),
161 text: format![
162 "The `{}` function accepts up to {max_positional_args} positional arguments",
163 function_name.str()
164 ],
165 },
166 ],
167 NoSuchArgument { function_name, argument } => vec![
168 ErrorLabel {
169 span: argument.span(),
170 text: format![
171 "an argument with name `{}` appears here",
172 argument.str(),
173 ],
174 },
175 ErrorLabel {
176 span: function_name.span(),
177 text: format![
178 "The `{}` function does not have a parameter with name `{}`",
179 function_name.str(),
180 argument.str(),
181 ],
182 },
183 ],
184 DuplicateArgument { parameter_name: _, first_assignment, second_assignment } => vec![
185 ErrorLabel {
186 span: first_assignment.span(),
187 text: "the first value appears here".to_string(),
188 },
189 ErrorLabel {
190 span: second_assignment.span(),
191 text: "the second value appear here".to_string(),
192 },
193 ],
194 NoSuchFunction { function_name } => vec![
195 ErrorLabel {
196 span: function_name.span(),
197 text: "there is no function with this name".to_string(),
198 },
199 ],
200 UnmatchedOpeningBracket{ open: close } => vec![
201 ErrorLabel {
202 span: close.span(),
203 text: "this bracket does not correspond to any subsequent closing bracket".to_string(),
204 },
205 ],
206 UnmatchedClosingBracket{ close } => vec![
207 ErrorLabel {
208 span: close.span(),
209 text: "this bracket does not correspond to any preceding opening bracket".to_string(),
210 },
211 ],
212 InvalidDimensionUnit { dimension: _, unit } => vec![
213 ErrorLabel {
214 span: unit.span(),
215 text: "valid units are the same as TeX".to_string(),
216 },
217 ],
218 UnexpectedToken { want: _, got, skipped } => vec![
219 ErrorLabel {
220 span: got.span(),
221 text: "this token was not expected".to_string(),
222 },
223 ErrorLabel {
224 span: skipped.span(),
225 text: "this source code will be skipped".to_string(),
226 },
227 ],
228 MissingArgsForFunction { function_name } => vec![
229 ErrorLabel {
230 span: function_name.span(),
231 text: "a function name must be followed by arguments".to_string(),
232 },
233 ],
234 MismatchedBraces { open, close } => vec![
235 ErrorLabel {
236 span: open.span(),
237 text: format!["the opening brace is '{open}'"],
238 },
239 ErrorLabel {
240 span: close.span(),
241 text: format!["the closing brace is '{close}'"],
242 },
243 ],
244 IncompleteKeywordArg { keyword } => vec![
245 ErrorLabel {
246 span: keyword.span(),
247 text: "a value was not provided for this keyword".into(),
248 },
249 ],
250MultipleDecimalPoints { point } => vec![
251 ErrorLabel {
252 span: point.span(),
253 text: "this is the second or subsequent decimal point".into(),
254 },
255],
256 NumberWithoutUnits { number } =>vec![
257 ErrorLabel {
258 span: number.span(),
259 text: "valid units are the same as TeX".to_string(),
260 },
261
262 ],
263 InvalidCharacter { char } => vec![
264 ErrorLabel {
265 span: char.span(),
266 text: "this character is not allowed here".into(),
267 },
268
269 ],
270 }
271 }
272 pub fn notes(&self) -> Vec<String> {
273 use Error::*;
274 match self {
275 PositionalArgAfterKeywordArg { .. } => {
276 vec![
277 "Positional arguments must be appear before keyword arguments (as in Python)"
278 .to_string(),
279 ]
280 }
281 UnmatchedClosingBracket { .. } | UnmatchedOpeningBracket { .. } => {
282 vec!["The token will be ignored".to_string()]
283 }
284 MismatchedBraces { .. } => {
285 vec![
286 "A '(' opening brace must be closed by ')' and similar for '[' and ']'"
287 .to_string(),
288 ]
289 }
290 UnexpectedToken { .. }
291 | MissingArgsForFunction { .. }
292 | IncorrectType { .. }
293 | TooManyPositionalArgs { .. }
294 | NoSuchArgument { .. }
295 | DuplicateArgument { .. }
296 | NoSuchFunction { .. }
297 | IncompleteKeywordArg { .. }
298 | InvalidDimensionUnit { .. }
299 | MultipleDecimalPoints { .. }
300 | NumberWithoutUnits { .. }
301 | InvalidCharacter { .. } => vec![],
302 }
303 }
304}
305
306pub struct ErrorLabel {
311 pub span: std::ops::Range<usize>,
312 pub text: String,
313}
314
315impl<'a> Error<'a> {
316 #[cfg(feature = "ariadne")]
317 pub fn ariadne_report(
318 &self,
319 file_name: &'a str,
320 ) -> ariadne::Report<'static, (&str, std::ops::Range<usize>)> {
321 let mut report =
322 ariadne::Report::build(ariadne::ReportKind::Error, (file_name, 0..file_name.len()))
323 .with_message(self.message());
324 let mut color = ariadne::Color::BrightRed;
325 for label in self.labels() {
326 report = report.with_label(
327 ariadne::Label::new((file_name, label.span))
328 .with_message(label.text)
329 .with_color(color),
330 );
331 color = ariadne::Color::BrightYellow;
332 }
333 for note in self.notes() {
334 report = report.with_note(note);
335 }
336 report.finish()
337 }
338}
339
340#[derive(Clone, Debug, Default)]
342pub struct ErrorAccumulator<'a> {
343 errs: std::rc::Rc<std::cell::RefCell<Vec<Error<'a>>>>,
344}
345
346impl<'a> ErrorAccumulator<'a> {
347 pub fn add(&self, err: Error<'a>) {
348 self.errs.borrow_mut().push(err);
349 }
350 pub fn len(&self) -> usize {
351 self.errs.borrow().len()
352 }
353 pub fn is_empty(&self) -> bool {
354 self.len() == 0
355 }
356 pub fn check(self) -> Result<(), Vec<Error<'a>>> {
357 let errs = self.errs.take();
358 if errs.is_empty() {
359 Ok(())
360 } else {
361 Err(errs)
362 }
363 }
364}
365
366#[cfg(test)]
367mod tests {
368 use super::*;
369
370 pub fn get_unique_err(source: &'_ str) -> Error<'_> {
371 let mut errs = super::super::parse_horizontal_list(source).unwrap_err();
372 assert_eq!(errs.len(), 1, "{:?}", errs);
373 errs.pop().unwrap()
374 }
375
376 macro_rules! error_tests {
377 ( $(
378 ($name: ident, $source: expr, $want_variant: ident,),
379 )+ ) => {
380 $(
381 #[test]
382 fn $name() {
383 let source = $source;
384 let err = get_unique_err(source);
385 println!["got: {err:?}"];
386 assert!(matches!(err, Error::$want_variant {..}), "{:?}", err);
387 }
388 )+
389 };
390 }
391 error_tests!(
392 (invalid_dimension_unit, "glue(0plx)", InvalidDimensionUnit,),
393 (invalid_type_positional, r#"chars(1pc)"#, IncorrectType,),
394 (invalid_type_keyword, r#"chars(content=1pc)"#, IncorrectType,),
395 (
396 too_many_positional_args_1,
397 r#"chars("Hello", 3, "Mundo")"#,
398 TooManyPositionalArgs,
399 ),
400 (
401 too_many_positional_args_2,
402 r#"chars("Hello", font=3, "Mundo")"#,
403 PositionalArgAfterKeywordArg,
404 ),
405 (
406 positional_arg_after_keyword_arg,
407 r#"chars(font=3, "Hello")"#,
408 PositionalArgAfterKeywordArg,
409 ),
410 (
411 duplicate_keyword_args,
412 r#"chars(content="Hello", content="World")"#,
413 DuplicateArgument,
414 ),
415 (
416 duplicate_positional_and_keyword_args,
417 r#"chars("Hello", content="Mundo")"#,
418 DuplicateArgument,
419 ),
420 (
421 invalid_keyword_arg,
422 r#"chars(random="Hello")"#,
423 NoSuchArgument,
424 ),
425 (invalid_func_name, r#"random()"#, NoSuchFunction,),
426 (trailing_closed_square, "chars()]", UnmatchedClosingBracket,),
427 (trailing_open_square, "chars())", UnmatchedClosingBracket,),
428 (
429 unexpected_token_before_func_name,
430 ",chars()",
431 UnexpectedToken,
432 ),
433 (
434 unexpected_token_after_func_name,
435 "chars,()",
436 UnexpectedToken,
437 ),
438 (missing_func_name, "()", UnexpectedToken,),
439 (
440 missing_paren_after_func_name,
441 "text",
442 MissingArgsForFunction,
443 ),
444 (incorrect_func_braces, "chars[]()", UnexpectedToken,),
445 (incorrect_closing_brace_round, "chars(]", MismatchedBraces,),
446 (
447 incorrect_closing_brace_square,
448 "hlist(content=[))",
449 MismatchedBraces,
450 ),
451 (
452 unmatched_opening_brace_round,
453 r#"chars("Hello""#,
454 UnmatchedOpeningBracket,
455 ),
456 (
457 unexpected_token_in_args_1,
458 "glue(,width=1pt)",
459 UnexpectedToken,
460 ),
461 (
462 unexpected_token_in_args_2,
463 "glue(width,=1pt)",
464 UnexpectedToken,
465 ),
466 (
467 unexpected_token_in_args_3,
468 "glue(width=,1pt)",
469 UnexpectedToken,
470 ),
471 (unexpected_token_in_args_4, "glue(,1pt)", UnexpectedToken,),
472 (end_of_input_in_args_1, "glue(width)", IncompleteKeywordArg,),
473 (end_of_input_in_args_2, "glue(width=)", IncompleteKeywordArg,),
474 (
475 two_decimal_points,
476 "glue(width=1.1.1pt)",
477 MultipleDecimalPoints,
478 ),
479 (number_no_unit, "glue(width=1.1)", NumberWithoutUnits,),
480 (random_character, "/", InvalidCharacter,),
481 (non_ascii_character, "รค", InvalidCharacter,),
482 );
483}