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