cas_parser/parser/
error.rs

1use ariadne::Fmt;
2use cas_attrs::ErrorKind;
3use cas_error::{ErrorKind, EXPR};
4use crate::tokenizer::TokenKind;
5use std::{collections::HashSet, ops::Range};
6
7/// An intentionally useless error. This should only be used for non-fatal errors, as it contains
8/// no useful information.
9#[derive(Debug, Clone, ErrorKind, PartialEq)]
10#[error(
11    message = "an internal non-fatal error occurred while parsing",
12    labels = ["here"],
13    help = "you should never see this error; please report this as a bug"
14)]
15pub struct NonFatal;
16
17/// Expected to see a certain kind of expression here.
18///
19/// The `expected` field should also contain the word "a" or "an" at the beginning to make the
20/// error grammatically correct.
21#[derive(Debug, Clone, ErrorKind, PartialEq)]
22#[error(
23    message = format!("expected {}", self.expected),
24    labels = [format!("I expected to see {} here", self.expected)],
25)]
26pub struct ExpectedExpr {
27    /// The kind of expression that was expected.
28    pub expected: &'static str,
29}
30
31/// The end of the source code was reached unexpectedly.
32#[derive(Debug, Clone, ErrorKind, PartialEq)]
33#[error(
34    message = "unexpected end of file",
35    labels = [format!("you might need to add another {} here", "expression".fg(EXPR))],
36)]
37pub struct UnexpectedEof;
38
39/// Unexpected end of an expression.
40#[derive(Debug, Clone, ErrorKind, PartialEq)]
41#[error(
42    message = "unexpected end of expression",
43    labels = [format!("I expected to see more {} here", "expression".fg(EXPR))],
44)]
45pub struct UnexpectedEoExpr;
46
47/// The end of the source code was expected, but something else was found.
48#[derive(Debug, Clone, ErrorKind, PartialEq)]
49#[error(
50    message = "expected end of file",
51    labels = [format!("I could not understand the remaining {} here", "expression".fg(EXPR))],
52)]
53pub struct ExpectedEof;
54
55/// An unexpected token was encountered.
56#[derive(Debug, Clone, ErrorKind, PartialEq)]
57#[error(
58    message = "unexpected token",
59    labels = [format!("expected one of: {}", self.expected.iter().map(|t| format!("{:?}", t)).collect::<Vec<_>>().join(", "))],
60    help = format!("found {:?}", self.found),
61)]
62pub struct UnexpectedToken {
63    /// The token(s) that were expected.
64    pub expected: &'static [TokenKind],
65
66    /// The token that was found.
67    pub found: TokenKind,
68}
69
70/// Encountered a keyword when a symbol name was expected.
71// TODO: this error is not currently reported, see `impl Parse for LitSym`
72#[derive(Debug, Clone, ErrorKind, PartialEq)]
73#[error(
74    message = "expected symbol name",
75    labels = [format!("found keyword `{}`", self.keyword)],
76    help = "you cannot use keywords as symbol names"
77)]
78pub struct ExpectedSymbolName {
79    /// The keyword that was found.
80    pub keyword: String,
81}
82
83/// The base used in radix notation was out of the allowed range.
84#[derive(Debug, Clone, ErrorKind, PartialEq)]
85#[error(
86    message = "invalid base in radix notation",
87    labels = [if self.too_large {
88        "this value is too large"
89    } else {
90        "this value is too small"
91    }],
92    help = format!("the base must be {}", "between 2 and 64, inclusive".fg(EXPR)),
93)]
94pub struct InvalidRadixBase {
95    /// The given base was too large. (Otherwise, it was too small.)
96    pub too_large: bool,
97}
98
99/// An invalid digit was used in a radix literal.
100#[derive(Debug, Clone, PartialEq)]
101pub struct InvalidRadixDigit {
102    /// The radix that was expected.
103    pub radix: u8,
104
105    /// The set of allowed digits for this radix.
106    pub allowed: &'static [char],
107
108    /// The invalid digits that were used.
109    pub digits: HashSet<char>,
110
111    /// If the last digit in the user's input is a `+` or `/` character, which happens to be a
112    /// valid character in base 64, this field contains the span of that character.
113    ///
114    /// The user may have been trying to add a number in radix notation to another, and mistakenly
115    /// placed the `+` or `/` at the end of the radix number instead of spaced apart.
116    pub last_op_digit: Option<(char, Range<usize>)>,
117}
118
119// manual ErrorKind implementation to support the `last_op_digit` field
120impl ErrorKind for InvalidRadixDigit {
121    fn build_report<'a>(
122        &self,
123        src_id: &'a str,
124        spans: &[std::ops::Range<usize>],
125    ) -> ariadne::Report<(&'a str, Range<usize>)> {
126        let labels = spans
127            .iter()
128            .cloned()
129            .map(|span| {
130                if let Some((_, last_op_digit)) = self.last_op_digit.as_ref() {
131                    // if one of the generated spans points to the last digit, remove that digit
132                    // from the generated span
133                    if span.end == last_op_digit.end {
134                        return span.start..span.end - 1;
135                    }
136                }
137
138                span
139            })
140            .filter(|span| span.start < span.end) // ^ that might have made the span empty
141            .map(|span| {
142                ariadne::Label::new((src_id, span))
143                    .with_color(cas_error::EXPR)
144            })
145            .chain(
146                self.last_op_digit.as_ref().map(|(ch, span)| {
147                    let operation = match ch {
148                        '+' => "add",
149                        '/' => "divide",
150                        _ => unreachable!(),
151                    };
152                    ariadne::Label::new((src_id, span.clone()))
153                        .with_message(format!(
154                            "if you're trying to {} two values, add a space between each value and this operator",
155                            operation
156                        ))
157                        .with_color(cas_error::EXPR)
158                }
159            ));
160
161        let mut builder =
162            ariadne::Report::build(ariadne::ReportKind::Error, src_id, spans[0].start)
163                .with_message(format!(
164                    "invalid digits in radix notation: `{}`",
165                    self.digits
166                        .iter()
167                        .map(|c| c.to_string())
168                        .collect::<Vec<_>>()
169                        .join("`, `"),
170                ))
171                .with_labels(labels);
172        builder.set_help(format!(
173            "base {} uses these digits (from lowest to highest value): {}",
174            self.radix,
175            self.allowed.iter().collect::<String>().fg(EXPR)
176        ));
177        builder.finish()
178    }
179}
180
181/// No number was provided in a radix literal.
182#[derive(Debug, Clone, ErrorKind, PartialEq)]
183#[error(
184    message = "missing value in radix notation",
185    labels = [format!("I was expecting to see a number in base {}, directly after this quote", self.radix)],
186    help = format!("base {} uses these digits (from lowest to highest value): {}", self.radix, self.allowed.iter().collect::<String>().fg(EXPR)),
187)]
188pub struct EmptyRadixLiteral {
189    /// The radix that was expected.
190    pub radix: u8,
191
192    /// The set of allowed digits for this radix.
193    pub allowed: &'static [char],
194}
195
196/// A parenthesis was not closed.
197#[derive(Debug, Clone, ErrorKind, PartialEq)]
198#[error(
199    message = "unclosed parenthesis",
200    labels = ["this parenthesis is not closed"],
201    help = if self.opening {
202        "add a closing parenthesis `)` somewhere after this"
203    } else {
204        "add an opening parenthesis `(` somewhere before this"
205    },
206)]
207pub struct UnclosedParenthesis {
208    /// Whether the parenthesis was an opening parenthesis `(`. Otherwise, the parenthesis was a
209    /// closing parenthesis `)`.
210    pub opening: bool,
211}
212
213/// The left-hand-side of an assignment was not a valid symbol or function header.
214#[derive(Debug, Clone, ErrorKind, PartialEq)]
215#[error(
216    message = "invalid left-hand-side of assignment operator",
217    labels = ["(1) this expression should be a symbol or function header...", "(2) ...to work with this assignment operator"],
218    help = if self.is_call {
219        "(1) looks like a function *call*, not a function *header*"
220    } else {
221        "maybe you meant to compare expressions with `==`?"
222    }
223)]
224pub struct InvalidAssignmentLhs {
225    /// Whether the expression span is pointing towards a function call.
226    pub is_call: bool,
227}
228
229/// Default arguments must be placed at the end of the argument list.
230#[derive(Debug, Clone, ErrorKind, PartialEq)]
231#[error(
232    message = "default arguments must be placed at the end of the function argument list",
233    labels = spans.iter()
234        .enumerate()
235        .map(|(i, _)| format!("default argument #{}", i + 1)),
236    help = "move the default argument(s) to the end",
237)]
238pub struct DefaultArgumentNotLast;
239
240/// The left-hand-side of a compound assignment operator cannot be a function header.
241#[derive(Debug, Clone, ErrorKind, PartialEq)]
242#[error(
243    message = "invalid left-hand-side of compound assignment operator",
244    labels = ["(1) this expression must be a symbol...", "(2) ...to work with this compound assignment operator"],
245    help = "use the standard assignment operator (`=`) instead",
246)]
247pub struct InvalidCompoundAssignmentLhs;
248
249/// A compound assignment operator was used within a function header.
250#[derive(Debug, Clone, ErrorKind, PartialEq)]
251#[error(
252    message = "cannot use compound assignment operator here",
253    labels = ["this operator"],
254    help = "only the standard assignment operator (`=`) is allowed in function headers",
255)]
256pub struct CompoundAssignmentInHeader;
257
258/// There were too many derivatives in prime notation.
259#[derive(Debug, Clone, ErrorKind, PartialEq)]
260#[error(
261    message = "too many derivatives in prime notation",
262    labels = ["you can only take at most 255 derivatives of a function"],
263    help = format!("I counted {} derivatives here", self.derivatives),
264)]
265pub struct TooManyDerivatives {
266    /// The number of derivatives that were found.
267    pub derivatives: usize,
268}
269
270/// Cannot use `return` outside of a function.
271#[derive(Debug, Clone, ErrorKind, PartialEq)]
272#[error(
273    message = "cannot use `return` keyword here",
274    labels = [""],
275    help = "`return` can only be used within a function definition"
276)]
277pub struct ReturnOutsideFunction;
278
279/// Missing `then` or `else` keyword in an `if` expression.
280#[derive(Debug, Clone, ErrorKind, PartialEq)]
281#[error(
282    message = format!("missing `{}` in `if` expression", self.keyword),
283    labels = ["this `if` expression".to_string(), format!("I expected to see `{}` here", self.keyword)],
284)]
285pub struct MissingIfKeyword {
286    /// The keyword that was expected.
287    pub keyword: &'static str,
288}
289
290/// Missing `then` or `else` branch in an `if` expression.
291#[derive(Debug, Clone, ErrorKind, PartialEq)]
292#[error(
293    message = format!("missing `{}` branch in `if` expression", self.keyword),
294    labels = ["this `if` expression".to_string(), format!("I expected to see an `{}` branch here", self.keyword)],
295)]
296pub struct MissingIfBranch {
297    /// The keyword that was expected.
298    pub keyword: &'static str,
299}
300
301/// Cannot use `then` keyword outside of an `if`, `while` or `for` expression.
302#[derive(Debug, Clone, ErrorKind, PartialEq)]
303#[error(
304    message = "cannot use `then` keyword here",
305    labels = [""],
306    help = "`then` can only be used directly after the condition in an `if` or `while` expression, or after the range in a `for` expression",
307)]
308pub struct ThenOutsideIfWhileFor;
309
310/// Cannot use `of` keyword outside of a `sum` or `product` expression.
311#[derive(Debug, Clone, ErrorKind, PartialEq)]
312#[error(
313    message = "cannot use `of` keyword here",
314    labels = [""],
315    help = "`of` can only be used directly after the range in a `sum` or `product` expression",
316)]
317pub struct OfOutsideSumProduct;
318
319/// Cannot use `break` outside of a loop.
320#[derive(Debug, Clone, ErrorKind, PartialEq)]
321#[error(
322    message = "cannot use `break` keyword here",
323    labels = [""],
324    help = "`break` or `continue` can only be used within a `loop`, `while`, or `for` expression",
325)]
326pub struct BreakOutsideLoop;
327
328/// Cannot use `continue` outside of a loop.
329#[derive(Debug, Clone, ErrorKind, PartialEq)]
330#[error(
331    message = "cannot use `continue` keyword here",
332    labels = [""],
333    help = "`break` or `continue` can only be used within a `loop`, `while`, or `for` expression",
334)]
335pub struct ContinueOutsideLoop;