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