1use ariadne::Fmt;
2use cas_attrs::ErrorKind;
3use cas_error::{ErrorKind, EXPR};
4use crate::tokenizer::TokenKind;
5use std::{collections::HashSet, ops::Range};
6
7#[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#[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#[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#[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 pub expected: &'static [TokenKind],
43
44 pub found: TokenKind,
46}
47
48#[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 pub keyword: String,
59}
60
61#[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 pub too_large: bool,
75}
76
77#[derive(Debug, Clone, PartialEq)]
79pub struct InvalidRadixDigit {
80 pub radix: u8,
82
83 pub allowed: &'static [char],
85
86 pub digits: HashSet<char>,
88
89 pub last_op_digit: Option<(char, Range<usize>)>,
95}
96
97impl 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 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) .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#[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 pub radix: u8,
169
170 pub allowed: &'static [char],
172}
173
174#[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 pub opening: bool,
189}
190
191#[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 pub is_call: bool,
205}
206
207#[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#[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#[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 pub derivatives: usize,
235}
236
237#[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 pub keyword: &'static str,
246}
247
248#[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 pub keyword: &'static str,
257}
258
259#[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#[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;