1use crate::{
2 ast::{CurveType, Span},
3 parser::token::Token,
4};
5use indoc::formatdoc;
6use itertools::Itertools;
7use miette::Diagnostic;
8use owo_colors::{OwoColorize, Stream::Stdout};
9use std::collections::HashSet;
10
11#[derive(Debug, Clone, Diagnostic, thiserror::Error)]
12#[error("{kind}\n")]
13#[diagnostic(
14 help(
15 "{}",
16 match kind {
17 ErrorKind::Unexpected(..) if !expected.is_empty() => {
18 format!(
19 "I am looking for one of the following patterns:\n{}",
20 expected
21 .iter()
22 .sorted()
23 .map(|x| format!(
24 "→ {}",
25 x.to_aiken()
26 .if_supports_color(Stdout, |s| s.purple())
27 ))
28 .collect::<Vec<_>>()
29 .join("\n")
30 )
31 },
32 _ => {
33 kind.help().map(|x| x.to_string()).unwrap_or_default()
34 }
35 }
36 )
37)]
38pub struct ParseError {
39 pub kind: ErrorKind,
40 #[label("{}", .label.unwrap_or_default())]
41 pub span: Span,
42 #[allow(dead_code)]
43 while_parsing: Option<(Span, &'static str)>,
44 expected: HashSet<Pattern>,
45 label: Option<&'static str>,
46}
47
48impl ParseError {
49 pub fn merge(mut self, other: Self) -> Self {
50 for expected in other.expected.into_iter() {
52 self.expected.insert(expected);
53 }
54 self
55 }
56
57 pub fn expected_but_got(expected: Pattern, got: Pattern, span: Span) -> Self {
58 Self {
59 kind: ErrorKind::Unexpected(got),
60 expected: HashSet::from_iter([expected]),
61 span,
62 while_parsing: None,
63 label: None,
64 }
65 }
66
67 pub fn invalid_assignment_right_hand_side(span: Span) -> Self {
68 Self {
69 kind: ErrorKind::UnfinishedAssignmentRightHandSide,
70 span,
71 while_parsing: None,
72 expected: HashSet::new(),
73 label: Some("invalid assignment right-hand side"),
74 }
75 }
76
77 pub fn invalid_tuple_index(span: Span, index: String, suffix: Option<String>) -> Self {
78 let hint = suffix.map(|suffix| format!("Did you mean '{index}{suffix}'?"));
79 Self {
80 kind: ErrorKind::InvalidTupleIndex { hint },
81 span,
82 while_parsing: None,
83 expected: HashSet::new(),
84 label: None,
85 }
86 }
87
88 pub fn deprecated_when_clause_guard(span: Span) -> Self {
89 Self {
90 kind: ErrorKind::DeprecatedWhenClause,
91 span,
92 while_parsing: None,
93 expected: HashSet::new(),
94 label: Some("deprecated"),
95 }
96 }
97
98 pub fn point_not_on_curve(curve: CurveType, span: Span) -> Self {
99 Self {
100 kind: ErrorKind::PointNotOnCurve { curve },
101 span,
102 while_parsing: None,
103 expected: HashSet::new(),
104 label: Some("out off curve"),
105 }
106 }
107
108 pub fn unknown_point_curve(curve: String, point: Option<String>, span: Span) -> Self {
109 let label = if point.is_some() {
110 Some("unknown curve")
111 } else {
112 Some("unknown point")
113 };
114
115 Self {
116 kind: ErrorKind::UnknownCurvePoint { curve, point },
117 span,
118 while_parsing: None,
119 expected: HashSet::new(),
120 label,
121 }
122 }
123
124 pub fn malformed_base16_string_literal(span: Span) -> Self {
125 Self {
126 kind: ErrorKind::MalformedBase16StringLiteral,
127 span,
128 while_parsing: None,
129 expected: HashSet::new(),
130 label: None,
131 }
132 }
133
134 pub fn malformed_base16_digits(span: Span) -> Self {
135 Self {
136 kind: ErrorKind::MalformedBase16Digits,
137 span,
138 while_parsing: None,
139 expected: HashSet::new(),
140 label: None,
141 }
142 }
143
144 pub fn hybrid_notation_in_bytearray(span: Span) -> Self {
145 Self {
146 kind: ErrorKind::HybridNotationInByteArray,
147 span,
148 while_parsing: None,
149 expected: HashSet::new(),
150 label: None,
151 }
152 }
153
154 pub fn match_on_curve(span: Span) -> Self {
155 Self {
156 kind: ErrorKind::PatternMatchOnCurvePoint,
157 span,
158 while_parsing: None,
159 expected: HashSet::new(),
160 label: Some("cannot pattern-match on curve point"),
161 }
162 }
163
164 pub fn match_string(span: Span) -> Self {
165 Self {
166 kind: ErrorKind::PatternMatchOnString,
167 span,
168 while_parsing: None,
169 expected: HashSet::new(),
170 label: Some("cannot pattern-match on string"),
171 }
172 }
173}
174
175impl PartialEq for ParseError {
176 fn eq(&self, other: &Self) -> bool {
177 self.kind == other.kind && self.span == other.span && self.label == other.label
178 }
179}
180
181impl<T: Into<Pattern>> chumsky::Error<T> for ParseError {
182 type Span = Span;
183
184 type Label = &'static str;
185
186 fn expected_input_found<Iter: IntoIterator<Item = Option<T>>>(
187 span: Self::Span,
188 expected: Iter,
189 found: Option<T>,
190 ) -> Self {
191 Self {
192 kind: found
193 .map(Into::into)
194 .map(ErrorKind::Unexpected)
195 .unwrap_or(ErrorKind::UnexpectedEnd),
196 span,
197 while_parsing: None,
198 expected: expected
199 .into_iter()
200 .map(|x| x.map(Into::into).unwrap_or(Pattern::End))
201 .collect(),
202 label: None,
203 }
204 }
205
206 fn with_label(mut self, label: Self::Label) -> Self {
207 self.label.get_or_insert(label);
208 self
209 }
210
211 fn merge(self, other: Self) -> Self {
212 ParseError::merge(self, other)
213 }
214}
215
216#[derive(Debug, Clone, PartialEq, Eq, Diagnostic, thiserror::Error)]
217pub enum ErrorKind {
218 #[error("I arrived at the end of the file unexpectedly.")]
219 UnexpectedEnd,
220
221 #[error("{0}")]
222 #[diagnostic(help("{}", .0.help().unwrap_or_else(|| Box::new("")))) ]
223 Unexpected(Pattern),
224
225 #[error("I discovered an invalid tuple index.")]
226 #[diagnostic()]
227 InvalidTupleIndex {
228 #[help]
229 hint: Option<String>,
230 },
231
232 #[error("I spotted an unfinished assignment.")]
233 #[diagnostic(
234 help(
235 "{} and {} bindings must be followed by a valid, complete, expression.",
236 "let".if_supports_color(Stdout, |s| s.yellow()),
237 "expect".if_supports_color(Stdout, |s| s.yellow()),
238 ),
239 )]
240 UnfinishedAssignmentRightHandSide,
241
242 #[error("I tripped over a {}", fmt_curve_type(.curve))]
243 PointNotOnCurve { curve: CurveType },
244
245 #[error("I tripped over a {}", fmt_unknown_curve(.curve, .point))]
246 UnknownCurvePoint {
247 curve: String,
248 point: Option<String>,
249 },
250
251 #[error("I tripped over a malformed hexadecimal digits.")]
252 #[diagnostic(help("{}", formatdoc! {
253 r#"When numbers starts with '0x', they are treated as hexadecimal numbers. Thus, only digits from 0-9 or letter from a-f (or A-F) can be used following a '0x' number declaration. Plus, hexadecimal digits always go by pairs, so the total number of digits must be even (not counting leading zeros)."#
254 }))]
255 MalformedBase16Digits,
256
257 #[error("I tripped over a malformed base16-encoded string literal.")]
258 #[diagnostic(help("{}", formatdoc! {
259 r#"You can declare literal bytearrays from base16-encoded (a.k.a. hexadecimal) string literals.
260
261 For example:
262
263 ┍━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
264 │ {} my_policy_id {}
265 │ #{}
266 "#,
267 "pub const".if_supports_color(Stdout, |s| s.bright_blue()),
268 "=".if_supports_color(Stdout, |s| s.yellow()),
269 "\"f4c9f9c4252d86702c2f4c2e49e6648c7cffe3c8f2b6b7d779788f50\""
270 .if_supports_color(Stdout, |s| s.bright_purple())
271 }))]
272 MalformedBase16StringLiteral,
273
274 #[error("I came across a bytearray declared using two different notations.")]
275 #[diagnostic(url("https://aiken-lang.org/language-tour/primitive-types#bytearray"))]
276 #[diagnostic(help("Either use decimal or hexadecimal notation, but don't mix them."))]
277 HybridNotationInByteArray,
278
279 #[error("I found a now-deprecated clause guard in a when/is expression.")]
280 #[diagnostic(help("{}", formatdoc! {
281 r#"Clause guards have been removed from Aiken. They were underused, considered potentially harmful and created needless complexity in the compiler. If you were using clause guards, our apologies, but you can now update your code and move the clause guards patterns inside a nested if/else expression.
282 "#
283 }))]
284 DeprecatedWhenClause,
285
286 #[error("I choked on a curve point in a bytearray pattern.")]
287 #[diagnostic(help(
288 "You can pattern-match on bytearrays just fine, but not on G1 nor G2 elements. Use if/else with an equality if you have to compare those."
289 ))]
290 PatternMatchOnCurvePoint,
291
292 #[error("I refuse to cooperate and match a utf-8 string.")]
293 #[diagnostic(help(
294 "You can pattern-match on bytearrays but not on strings. Note that I can parse utf-8 encoded bytearrays just fine, so you probably want to drop the extra '@' and only manipulate bytearrays wherever you need to. On-chain, strings shall be avoided as much as possible."
295 ))]
296 PatternMatchOnString,
297}
298
299fn fmt_curve_type(curve: &CurveType) -> String {
300 match curve {
301 CurveType::Bls12_381(point) => {
302 format!("{point} point that is not in the bls12_381 curve")
303 }
304 }
305}
306
307fn fmt_unknown_curve(curve: &String, point: &Option<String>) -> String {
308 match point {
309 Some(point) => {
310 format!(
311 "{} which is an unknown point for curve {}",
312 point.if_supports_color(Stdout, |s| s.purple()),
313 curve.if_supports_color(Stdout, |s| s.purple()),
314 )
315 }
316 None => {
317 format!(
318 "{} which is an unknown curve",
319 curve.if_supports_color(Stdout, |s| s.purple())
320 )
321 }
322 }
323}
324
325#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Diagnostic, thiserror::Error)]
326pub enum Pattern {
327 #[error("I found an unexpected char '{0:?}'.")]
328 #[diagnostic(help("Try removing it!"))]
329 Char(char),
330 #[error("I found an unexpected token '{0}'.")]
331 #[diagnostic(help("Try removing it!"))]
332 Token(Token),
333 #[error("I found an unexpected end of input.")]
334 End,
335 #[error("I found a malformed list spread pattern.")]
336 #[diagnostic(help("List spread in matches can use a discard '_' or var."))]
337 Match,
338 #[error("I found an out-of-bound byte literal.")]
339 #[diagnostic(help("Bytes must be between 0-255."))]
340 Byte,
341 #[error("I found an unexpected label.")]
342 #[diagnostic(help("You can only use labels surrounded by curly braces"))]
343 Label,
344 #[error("I found an unexpected discard '_'.")]
345 #[diagnostic(help("You can only use capture syntax with functions not constructors."))]
346 Discard,
347}
348
349impl Pattern {
350 fn to_aiken(&self) -> String {
351 use Pattern::*;
352 match self {
353 Token(tok) => tok.to_string(),
354 Char(c) => c.to_string(),
355 End => "<END OF FILE>".to_string(),
356 Match => "A pattern (a discard, a var, etc...)".to_string(),
357 Byte => "A byte between [0; 255]".to_string(),
358 Label => "A label".to_string(),
359 Discard => "_".to_string(),
360 }
361 }
362}
363
364impl From<char> for Pattern {
365 fn from(c: char) -> Self {
366 Self::Char(c)
367 }
368}
369
370impl From<Token> for Pattern {
371 fn from(tok: Token) -> Self {
372 Self::Token(tok)
373 }
374}