boxworks/lang/
error.rs

1use super::Str;
2
3/// Error encountered when parsing Box language.
4#[derive(Debug, PartialEq, Eq)]
5pub enum Error<'a> {
6    /// A positional argument appears after a keyword argument.
7    ///
8    /// This is an error, just like in Python.
9    /// The problem is that it's ambiguous which parameter the
10    /// positional argument should apply to.
11    PositionalArgAfterKeywordArg {
12        positional_arg: Str<'a>,
13        keyword_arg: Str<'a>,
14    },
15
16    /// A function argument has the wrong type.
17    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    /// Too many positional arguments provided.
26    TooManyPositionalArgs {
27        extra_positional_arg: Str<'a>,
28        function_name: Str<'a>,
29        max_positional_args: usize,
30    },
31
32    /// No such argument to this function.
33    NoSuchArgument {
34        function_name: Str<'a>,
35        argument: Str<'a>,
36    },
37
38    /// The same argument was provided multiple times.
39    DuplicateArgument {
40        parameter_name: &'a str,
41        first_assignment: Str<'a>,
42        second_assignment: Str<'a>,
43    },
44
45    /// The specified function does not exist.
46    NoSuchFunction { function_name: Str<'a> },
47
48    /// An opening bracket token '[' or '(' was unmatched.
49    UnmatchedOpeningBracket { open: Str<'a> },
50
51    /// A closing bracket token ']' or ')' was unmatched.
52    UnmatchedClosingBracket { close: Str<'a> },
53
54    /// An invalid unit was provided for a dimension.
55    InvalidDimensionUnit { dimension: Str<'a>, unit: Str<'a> },
56
57    /// An unexpected token was encountered.
58    UnexpectedToken {
59        want: &'static str,
60        got: Str<'a>,
61        skipped: Str<'a>,
62    },
63
64    /// An unknown escape sequence like `\a`.
65    UnknownEscapeSequence { sequence: Str<'a> },
66
67    /// A function name appeared without any arguments
68    MissingArgsForFunction { function_name: Str<'a> },
69
70    /// Mismatched braces - e.g. [) or (].
71    MismatchedBraces { open: Str<'a>, close: Str<'a> },
72
73    /// Input ended while parsing a function keyword argument
74    IncompleteKeywordArg { keyword: Str<'a> },
75
76    /// A number has multiple decimal points.
77    MultipleDecimalPoints { point: Str<'a> },
78
79    /// No units were specified for a number.
80    NumberWithoutUnits { number: Str<'a> },
81
82    /// Input contains an invalid character (like a non-ASCII character)
83    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
319/// Label on an error message.
320///
321/// A label identifies a particular piece of source code and some
322/// information about it.
323pub 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/// A data structure for accumulating errors.
354#[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}