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