1use crate::ast::{SrcSpan, TypeAst};
2use crate::diagnostic::{ExtraLabel, Label};
3use crate::error::wrap;
4use crate::parse::Token;
5use ecow::EcoString;
6use itertools::Itertools;
7
8#[derive(Debug, PartialEq, Eq, Clone, Copy)]
9pub struct LexicalError {
10 pub error: LexicalErrorType,
11 pub location: SrcSpan,
12}
13
14#[derive(Debug, PartialEq, Eq, Clone, Copy)]
15pub enum InvalidUnicodeEscapeError {
16 MissingOpeningBrace, // Expected '{'
17 ExpectedHexDigitOrCloseBrace, // Expected hex digit or '}'
18 InvalidNumberOfHexDigits, // Expected between 1 and 6 hex digits
19 InvalidCodepoint, // Invalid Unicode codepoint
20}
21
22#[derive(Debug, PartialEq, Eq, Clone, Copy)]
23pub enum LexicalErrorType {
24 BadStringEscape, // string contains an unescaped slash
25 InvalidUnicodeEscape(InvalidUnicodeEscapeError), // \u{...} escape sequence is invalid
26 DigitOutOfRadix, // 0x012 , 2 is out of radix
27 NumTrailingUnderscore, // 1_000_ is not allowed
28 RadixIntNoValue, // 0x, 0b, 0o without a value
29 MissingExponent, // 1.0e, for example, where there is no exponent
30 UnexpectedStringEnd, // Unterminated string literal
31 UnrecognizedToken { tok: char },
32 InvalidTripleEqual,
33}
34
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct ParseError {
37 pub error: ParseErrorType,
38 pub location: SrcSpan,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub enum ParseErrorType {
43 ExpectedEqual, // expect "="
44 ExpectedExpr, // after "->" in a case clause
45 ExpectedName, // any token used when a Name was expected
46 ExpectedPattern, // after ':' where a pattern is expected
47 ExpectedType, // after ':' or '->' where a type annotation is expected
48 ExpectedUpName, // any token used when a UpName was expected
49 ExpectedValue, // no value after "="
50 ExpectedDefinition, // after attributes
51 ExpectedDeprecationMessage, // after "deprecated"
52 ExpectedFunctionDefinition, // after function-only attributes
53 ExpectedTargetName, // after "@target("
54 ExprLparStart, // it seems "(" was used to start an expression
55 ExtraSeparator, // #(1,,) <- the 2nd comma is an extra separator
56 IncorrectName, // UpName or DiscardName used when Name was expected
57 IncorrectUpName, // Name or DiscardName used when UpName was expected
58 InvalidBitArraySegment, // <<7:hello>> `hello` is an invalid BitArray segment
59 InvalidBitArrayUnit, // in <<1:unit(x)>> x must be 1 <= x <= 256
60 InvalidTailPattern, // only name and _name are allowed after ".." in list pattern
61 InvalidTupleAccess, // only positive int literals for tuple access
62 LexError {
63 error: LexicalError,
64 },
65 NestedBitArrayPattern, // <<<<1>>, 2>>, <<1>> is not allowed in there
66 NoLetBinding, // Bindings and rebinds always require let and must always bind to a value.
67 NoValueAfterEqual, // = <something other than a value>
68 NotConstType, // :fn(), name, _ are not valid const types
69 OpNakedRight, // Operator with no value to the right
70 OpaqueTypeAlias, // Type aliases cannot be opaque
71 TooManyArgHoles, // a function call can have at most 1 arg hole
72 DuplicateAttribute, // an attribute was used more than once
73 UnknownAttribute, // an attribute was used that is not known
74 UnknownTarget, // an unknown target was used
75 ListSpreadWithoutElements, // Pointless spread: `[..xs]`
76 ListSpreadFollowedByElements, // trying to append something after the spread: `[..xs, x]`
77 ListSpreadWithAnotherSpread {
78 first_spread_location: SrcSpan,
79 }, // trying to use multiple spreads: `[..xs, ..ys]`
80 LowcaseBooleanPattern, // most likely user meant True or False in patterns
81 UnexpectedLabel, // argument labels were provided, but are not supported in this context
82 UnexpectedEof,
83 UnexpectedReservedWord, // reserved word used when a name was expected
84 UnexpectedToken {
85 token: Token,
86 expected: Vec<EcoString>,
87 hint: Option<EcoString>,
88 },
89 UnexpectedFunction, // a function was used called outside of another function
90 // A variable was assigned or discarded on the left hand side of a <> pattern
91 ConcatPatternVariableLeftHandSide,
92 ListSpreadWithoutTail, // let x = [1, ..]
93 ExpectedFunctionBody, // let x = fn()
94 RedundantInternalAttribute, // for a private definition marked as internal
95 InvalidModuleTypePattern, // for patterns that have a dot like: `name.thing`
96 ListPatternSpreadFollowedByElements, // When there is a pattern after a spread [..rest, pattern]
97 ExpectedRecordConstructor {
98 name: EcoString,
99 public: bool,
100 opaque: bool,
101 field: EcoString,
102 field_type: Option<Box<TypeAst>>,
103 },
104 CallInClauseGuard, // case x { _ if f() -> 1 }
105 IfExpression,
106 ConstantRecordConstructorNoArguments, // const x = Record()
107 TypeConstructorNoArguments, // let a : Int()
108 TypeDefinitionNoArguments, // pub type Wibble() { ... }
109 UnknownAttributeRecordVariant, // an attribute was used that is not know for a custom type variant
110 // a Python-like import was written, such as `import gleam.io`, instead of `import gleam/io`
111 IncorrectImportModuleSeparator {
112 module: EcoString,
113 item: EcoString,
114 },
115}
116
117pub(crate) struct ParseErrorDetails {
118 pub text: String,
119 pub label_text: EcoString,
120 pub extra_labels: Vec<ExtraLabel>,
121 pub hint: Option<String>,
122}
123
124impl ParseErrorType {
125 pub(crate) fn details(&self) -> ParseErrorDetails {
126 match self {
127 ParseErrorType::ExpectedEqual => ParseErrorDetails {
128 text: "".into(),
129 hint: None,
130 label_text: "I was expecting a '=' after this".into(),
131 extra_labels: vec![],
132 },
133
134 ParseErrorType::ExpectedExpr => ParseErrorDetails {
135 text: "".into(),
136 hint: None,
137 label_text: "I was expecting an expression after this".into(),
138 extra_labels: vec![],
139 },
140
141 ParseErrorType::ExpectedName => ParseErrorDetails {
142 text: "".into(),
143 hint: None,
144 label_text: "I was expecting a name here".into(),
145 extra_labels: vec![],
146 },
147
148 ParseErrorType::ExpectedPattern => ParseErrorDetails {
149 text: "".into(),
150 hint: None,
151 label_text: "I was expecting a pattern after this".into(),
152 extra_labels: vec![],
153 },
154
155 ParseErrorType::ExpectedType => ParseErrorDetails {
156 text: "See: https://tour.gleam.run/basics/assignments/".into(),
157 hint: None,
158 label_text: "I was expecting a type after this".into(),
159 extra_labels: vec![],
160 },
161
162 ParseErrorType::ExpectedUpName => ParseErrorDetails {
163 text: "".into(),
164 hint: None,
165 label_text: "I was expecting a type name here".into(),
166 extra_labels: vec![],
167 },
168
169 ParseErrorType::ExpectedValue => ParseErrorDetails {
170 text: "".into(),
171 hint: None,
172 label_text: "I was expecting a value after this".into(),
173 extra_labels: vec![],
174 },
175
176 ParseErrorType::ExpectedDefinition => ParseErrorDetails {
177 text: "".into(),
178 hint: None,
179 label_text: "I was expecting a definition after this".into(),
180 extra_labels: vec![],
181 },
182
183 ParseErrorType::ExpectedDeprecationMessage => ParseErrorDetails {
184 text: "".into(),
185 hint: None,
186 label_text: "A deprecation attribute must have a string message.".into(),
187 extra_labels: vec![],
188 },
189
190 ParseErrorType::ExpectedFunctionDefinition => ParseErrorDetails {
191 text: "".into(),
192 hint: None,
193 label_text: "I was expecting a function definition after this".into(),
194 extra_labels: vec![],
195 },
196
197 ParseErrorType::ExpectedTargetName => ParseErrorDetails {
198 text: "Try `erlang`, `javascript`.".into(),
199 hint: None,
200 label_text: "I was expecting a target name after this".into(),
201 extra_labels: vec![],
202 },
203
204 ParseErrorType::ExtraSeparator => ParseErrorDetails {
205 text: "".into(),
206 hint: Some("Try removing it?".into()),
207 label_text: "This is an extra delimiter".into(),
208 extra_labels: vec![],
209 },
210
211 ParseErrorType::ExprLparStart => ParseErrorDetails {
212 text: "".into(),
213 hint: Some(
214 "To group expressions in Gleam, use \"{\" and \"}\"; \
215tuples are created with `#(` and `)`."
216 .into(),
217 ),
218 label_text: "This parenthesis cannot be understood here".into(),
219 extra_labels: vec![],
220 },
221
222 ParseErrorType::IncorrectName => ParseErrorDetails {
223 text: "".into(),
224 hint: Some(wrap(
225 "Variable and module names start with a lowercase letter, \
226and can contain a-z, 0-9, or _.",
227 )),
228 label_text: "I'm expecting a lowercase name here".into(),
229 extra_labels: vec![],
230 },
231
232 ParseErrorType::IncorrectUpName => ParseErrorDetails {
233 text: "".into(),
234 hint: Some(wrap(
235 "Type names start with a uppercase letter, and can \
236contain a-z, A-Z, or 0-9.",
237 )),
238 label_text: "I'm expecting a type name here".into(),
239 extra_labels: vec![],
240 },
241
242 ParseErrorType::InvalidBitArraySegment => ParseErrorDetails {
243 text: "See: https://tour.gleam.run/data-types/bit-arrays/".into(),
244 hint: Some(format!(
245 "Valid BitArray segment options are:\n{}",
246 wrap(
247 "bits, bytes, int, float, utf8, utf16, utf32, utf8_codepoint, \
248utf16_codepoint, utf32_codepoint, signed, unsigned, big, little, native, size, unit.",
249 )
250 )),
251 label_text: "This is not a valid BitArray segment option".into(),
252 extra_labels: vec![],
253 },
254
255 ParseErrorType::InvalidBitArrayUnit => ParseErrorDetails {
256 text: "See: https://tour.gleam.run/data-types/bit-arrays/".into(),
257 hint: Some("Unit must be an integer literal >= 1 and <= 256.".into()),
258 label_text: "This is not a valid BitArray unit value".into(),
259 extra_labels: vec![],
260 },
261
262 ParseErrorType::InvalidTailPattern => ParseErrorDetails {
263 text: "".into(),
264 hint: None,
265 label_text: "This part of a list pattern can only be a name or a discard".into(),
266 extra_labels: vec![],
267 },
268
269 ParseErrorType::InvalidTupleAccess => ParseErrorDetails {
270 text: "".into(),
271 hint: Some(
272 "Only non negative integer literals like 0, or 1_000 can be used.".into(),
273 ),
274 label_text: "This integer is not valid for tuple access".into(),
275 extra_labels: vec![],
276 },
277
278 ParseErrorType::LexError { error: lex_err } => {
279 let (label_text, text_lines) = lex_err.to_parse_error_info();
280 let text = text_lines.join("\n");
281 ParseErrorDetails {
282 text,
283 hint: None,
284 label_text: label_text.into(),
285 extra_labels: vec![],
286 }
287 }
288
289 ParseErrorType::NestedBitArrayPattern => ParseErrorDetails {
290 text: "".into(),
291 hint: None,
292 label_text: "BitArray patterns cannot be nested".into(),
293 extra_labels: vec![],
294 },
295
296 ParseErrorType::NotConstType => ParseErrorDetails {
297 text: "See: https://tour.gleam.run/basics/constants/".into(),
298 hint: None,
299 label_text: "This type is not allowed in module constants".into(),
300 extra_labels: vec![],
301 },
302
303 ParseErrorType::NoLetBinding => ParseErrorDetails {
304 text: "See: https://tour.gleam.run/basics/assignments/".into(),
305 hint: Some("Use let for binding.".into()),
306 label_text: "There must be a 'let' to bind variable to value".into(),
307 extra_labels: vec![],
308 },
309
310 ParseErrorType::NoValueAfterEqual => ParseErrorDetails {
311 text: "".into(),
312 hint: None,
313 label_text: "I was expecting to see a value after this equals sign".into(),
314 extra_labels: vec![],
315 },
316
317 ParseErrorType::OpaqueTypeAlias => ParseErrorDetails {
318 text: "See: https://tour.gleam.run/basics/type-aliases/".into(),
319 hint: None,
320 label_text: "Type Aliases cannot be opaque".into(),
321 extra_labels: vec![],
322 },
323
324 ParseErrorType::OpNakedRight => ParseErrorDetails {
325 text: "".into(),
326 hint: Some("Remove it or put a value after it.".into()),
327 label_text: "This operator has no value on its right side".into(),
328 extra_labels: vec![],
329 },
330
331 ParseErrorType::TooManyArgHoles => ParseErrorDetails {
332 text: "See: https://tour.gleam.run/functions/functions/".into(),
333 hint: Some("Function calls can have at most one argument hole.".into()),
334 label_text: "There is more than 1 argument hole in this function call".into(),
335 extra_labels: vec![],
336 },
337
338 ParseErrorType::UnexpectedEof => ParseErrorDetails {
339 text: "".into(),
340 hint: None,
341 label_text: "The module ended unexpectedly".into(),
342 extra_labels: vec![],
343 },
344
345 ParseErrorType::ListSpreadWithoutElements => ParseErrorDetails {
346 text: "See: https://tour.gleam.run/basics/lists/".into(),
347 hint: Some("Try prepending some elements [1, 2, ..list].".into()),
348 label_text: "This spread does nothing".into(),
349 extra_labels: vec![],
350 },
351
352 ParseErrorType::ListSpreadWithAnotherSpread {
353 first_spread_location,
354 } => ParseErrorDetails {
355 text: [
356 "Lists are immutable and singly-linked, so to join two or more lists",
357 "all the elements of the lists would need to be copied into a new list.",
358 "This would be slow, so there is no built-in syntax for it.",
359 ]
360 .join("\n"),
361 hint: None,
362 label_text: "I wasn't expecting a second spread here".into(),
363 extra_labels: vec![ExtraLabel {
364 src_info: None,
365 label: Label {
366 text: Some("You're using a spread here".into()),
367 span: *first_spread_location,
368 },
369 }],
370 },
371
372 ParseErrorType::ListSpreadFollowedByElements => ParseErrorDetails {
373 text: [
374 "Lists are immutable and singly-linked, so to append items to them",
375 "all the elements of a list would need to be copied into a new list.",
376 "This would be slow, so there is no built-in syntax for it.",
377 "",
378 ]
379 .join("\n"),
380 hint: Some(
381 "Prepend items to the list and then reverse it once you are done.".into(),
382 ),
383 label_text: "I wasn't expecting elements after this".into(),
384 extra_labels: vec![],
385 },
386
387 ParseErrorType::ListPatternSpreadFollowedByElements => ParseErrorDetails {
388 text: [
389 "Lists are immutable and singly-linked, so to match on the end",
390 "of a list would require the whole list to be traversed. This",
391 "would be slow, so there is no built-in syntax for it. Pattern",
392 "match on the start of the list instead.",
393 ]
394 .join("\n"),
395 hint: None,
396 label_text: "I wasn't expecting elements after this".into(),
397 extra_labels: vec![],
398 },
399
400 ParseErrorType::UnexpectedReservedWord => ParseErrorDetails {
401 text: "".into(),
402 hint: Some("I was expecting to see a name here.".into()),
403 label_text: "This is a reserved word".into(),
404 extra_labels: vec![],
405 },
406
407 ParseErrorType::LowcaseBooleanPattern => ParseErrorDetails {
408 text: "See: https://tour.gleam.run/basics/bools/".into(),
409 hint: Some("In Gleam boolean literals are `True` and `False`.".into()),
410 label_text: "Did you want a Bool instead of a variable?".into(),
411 extra_labels: vec![],
412 },
413
414 ParseErrorType::UnexpectedLabel => ParseErrorDetails {
415 text: "Please remove the argument label.".into(),
416 hint: None,
417 label_text: "Argument labels are not allowed for anonymous functions".into(),
418 extra_labels: vec![],
419 },
420
421 ParseErrorType::UnexpectedToken {
422 token,
423 expected,
424 hint,
425 } => {
426 let found = match token {
427 Token::Int { .. } => "an Int".to_string(),
428 Token::Float { .. } => "a Float".to_string(),
429 Token::String { .. } => "a String".to_string(),
430 Token::CommentDoc { .. } => "a comment".to_string(),
431 Token::DiscardName { .. } => "a discard name".to_string(),
432 Token::Name { .. } | Token::UpName { .. } => "a name".to_string(),
433 _ if token.is_reserved_word() => format!("the keyword {token}"),
434 _ => token.to_string(),
435 };
436
437 let messages = std::iter::once(format!("Found {found}, expected one of: "))
438 .chain(expected.iter().map(|s| format!("- {s}")));
439
440 let messages = match hint {
441 Some(hint_text) => messages
442 .chain(std::iter::once(format!("Hint: {hint_text}")))
443 .collect_vec(),
444 _ => messages.collect(),
445 };
446
447 ParseErrorDetails {
448 text: messages.join("\n"),
449 hint: None,
450 label_text: "I was not expecting this".into(),
451 extra_labels: vec![],
452 }
453 }
454
455 ParseErrorType::ConcatPatternVariableLeftHandSide => ParseErrorDetails {
456 text: [
457 "We can't tell what size this prefix should be so we don't know",
458 "how to handle this pattern.",
459 "",
460 "If you want to match one character consider using `pop_grapheme`",
461 "from the stdlib's `gleam/string` module.",
462 ]
463 .join("\n"),
464 hint: None,
465 label_text: "This must be a string literal".into(),
466 extra_labels: vec![],
467 },
468
469 ParseErrorType::UnexpectedFunction => ParseErrorDetails {
470 text: "".into(),
471 hint: None,
472 label_text: "Functions can only be called within other functions".into(),
473 extra_labels: vec![],
474 },
475
476 ParseErrorType::ListSpreadWithoutTail => ParseErrorDetails {
477 text: "If a list expression has a spread then a tail must also be given.".into(),
478 hint: None,
479 label_text: "I was expecting a value after this spread".into(),
480 extra_labels: vec![],
481 },
482
483 ParseErrorType::UnknownAttribute => ParseErrorDetails {
484 text: "".into(),
485 hint: Some("Try `deprecated`, `external` or `target` instead.".into()),
486 label_text: "I don't recognise this attribute".into(),
487 extra_labels: vec![],
488 },
489
490 ParseErrorType::DuplicateAttribute => ParseErrorDetails {
491 text: "This attribute has already been given.".into(),
492 hint: None,
493 label_text: "Duplicate attribute".into(),
494 extra_labels: vec![],
495 },
496
497 ParseErrorType::UnknownTarget => ParseErrorDetails {
498 text: "Try `erlang`, `javascript`.".into(),
499 hint: None,
500 label_text: "I don't recognise this target".into(),
501 extra_labels: vec![],
502 },
503
504 ParseErrorType::ExpectedFunctionBody => ParseErrorDetails {
505 text: "".into(),
506 hint: None,
507 label_text: "This function does not have a body".into(),
508 extra_labels: vec![],
509 },
510
511 ParseErrorType::RedundantInternalAttribute => ParseErrorDetails {
512 text: "Only a public definition can be annotated as internal.".into(),
513 hint: Some("Remove the `@internal` annotation.".into()),
514 label_text: "Redundant internal attribute".into(),
515 extra_labels: vec![],
516 },
517
518 ParseErrorType::InvalidModuleTypePattern => ParseErrorDetails {
519 text: [
520 "I'm expecting a pattern here",
521 "Hint: A pattern can be a constructor name, a literal value",
522 "or a variable to bind a value to, etc.",
523 "See: https://tour.gleam.run/flow-control/case-expressions/",
524 ]
525 .join("\n"),
526 hint: None,
527 label_text: "Invalid pattern".into(),
528 extra_labels: vec![],
529 },
530
531 ParseErrorType::ExpectedRecordConstructor {
532 name,
533 public,
534 opaque,
535 field,
536 field_type,
537 } => {
538 let (accessor, opaque) = match *public {
539 true if *opaque => ("pub ", "opaque "),
540 true => ("pub ", ""),
541 false => ("", ""),
542 };
543
544 let mut annotation = EcoString::new();
545 match field_type {
546 Some(t) => t.print(&mut annotation),
547 None => annotation.push_str("Type"),
548 };
549
550 ParseErrorDetails {
551 text: [
552 "Each custom type variant must have a constructor:\n".into(),
553 format!("{accessor}{opaque}type {name} {{"),
554 format!(" {name}("),
555 format!(" {field}: {annotation},"),
556 " )".into(),
557 "}".into(),
558 ]
559 .join("\n"),
560 hint: None,
561 label_text: "I was not expecting this".into(),
562 extra_labels: vec![],
563 }
564 }
565
566 ParseErrorType::CallInClauseGuard => ParseErrorDetails {
567 text: "Functions cannot be called in clause guards.".into(),
568 hint: None,
569 label_text: "Unsupported expression".into(),
570 extra_labels: vec![],
571 },
572
573 ParseErrorType::IfExpression => ParseErrorDetails {
574 text: [
575 "If you want to write a conditional expression you can use a `case`:",
576 "",
577 " case condition {",
578 " True -> todo",
579 " False -> todo",
580 " }",
581 "",
582 "See: https://tour.gleam.run/flow-control/case-expressions/",
583 ]
584 .join("\n"),
585 hint: None,
586 label_text: "Gleam doesn't have if expressions".into(),
587 extra_labels: vec![],
588 },
589
590 ParseErrorType::ConstantRecordConstructorNoArguments => ParseErrorDetails {
591 text: "A record must be passed arguments when constructed.".into(),
592 hint: None,
593 label_text: "I was expecting arguments here".into(),
594 extra_labels: vec![],
595 },
596
597 ParseErrorType::TypeConstructorNoArguments => ParseErrorDetails {
598 text: "A type constructor must be passed arguments.".into(),
599 hint: None,
600 label_text: "I was expecting arguments here".into(),
601 extra_labels: vec![],
602 },
603
604 ParseErrorType::TypeDefinitionNoArguments => ParseErrorDetails {
605 text: "A generic type must have at least a generic parameter.".into(),
606 hint: Some("If a type is not generic you should omit the `()`.".into()),
607 label_text: "I was expecting generic parameters here".into(),
608 extra_labels: vec![],
609 },
610
611 ParseErrorType::UnknownAttributeRecordVariant => ParseErrorDetails {
612 text: "".into(),
613 hint: Some("Did you mean `@deprecated`?".into()),
614 label_text: "This attribute cannot be used on a variant.".into(),
615 extra_labels: vec![],
616 },
617
618 ParseErrorType::IncorrectImportModuleSeparator { module, item } => ParseErrorDetails {
619 text: [
620 "Perhaps you meant one of:".into(),
621 "".into(),
622 format!(" import {module}/{item}"),
623 format!(" import {module}.{{item}}"),
624 ]
625 .join("\n"),
626 hint: None,
627 label_text: "I was expecting either `/` or `.{` here.".into(),
628 extra_labels: vec![],
629 },
630 }
631 }
632}
633
634impl LexicalError {
635 pub fn to_parse_error_info(&self) -> (&'static str, Vec<String>) {
636 match &self.error {
637 LexicalErrorType::BadStringEscape => (
638 "I don't understand this escape code",
639 vec![
640 "Hint: Add another backslash before it.".into(),
641 "See: https://tour.gleam.run/basics/strings".into(),
642 ],
643 ),
644 LexicalErrorType::DigitOutOfRadix => {
645 ("This digit is too big for the specified radix", vec![])
646 }
647 LexicalErrorType::NumTrailingUnderscore => (
648 "Numbers cannot have a trailing underscore",
649 vec!["Hint: remove it.".into()],
650 ),
651 LexicalErrorType::RadixIntNoValue => ("This integer has no value", vec![]),
652 LexicalErrorType::MissingExponent => (
653 "This float is missing an exponent",
654 vec!["Hint: Add an exponent or remove the trailing `e`".into()],
655 ),
656 LexicalErrorType::UnexpectedStringEnd => {
657 ("The string starting here was left open", vec![])
658 }
659 LexicalErrorType::UnrecognizedToken { tok } if *tok == ';' => (
660 "Remove this semicolon",
661 vec![
662 "Hint: Semicolons used to be whitespace and did nothing.".into(),
663 "You can safely remove them without your program changing.".into(),
664 ],
665 ),
666 LexicalErrorType::UnrecognizedToken { tok } if *tok == '\'' => (
667 "Unexpected single quote",
668 vec!["Hint: Strings are written with double quotes.".into()],
669 ),
670 LexicalErrorType::UnrecognizedToken { .. } => (
671 "I can't figure out what to do with this character",
672 vec!["Hint: Is it a typo?".into()],
673 ),
674 LexicalErrorType::InvalidUnicodeEscape(
675 InvalidUnicodeEscapeError::MissingOpeningBrace,
676 ) => (
677 "Expected '{' in Unicode escape sequence",
678 vec!["Hint: Add it.".into()],
679 ),
680 LexicalErrorType::InvalidUnicodeEscape(
681 InvalidUnicodeEscapeError::ExpectedHexDigitOrCloseBrace,
682 ) => (
683 "Expected hex digit or '}' in Unicode escape sequence",
684 vec![
685 "Hint: Hex digits are digits from 0 to 9 and letters from a to f or A to F."
686 .into(),
687 ],
688 ),
689 LexicalErrorType::InvalidUnicodeEscape(
690 InvalidUnicodeEscapeError::InvalidNumberOfHexDigits,
691 ) => (
692 "Expected between 1 and 6 hex digits in Unicode escape sequence",
693 vec![],
694 ),
695 LexicalErrorType::InvalidUnicodeEscape(InvalidUnicodeEscapeError::InvalidCodepoint) => {
696 ("Invalid Unicode codepoint", vec![])
697 }
698 LexicalErrorType::InvalidTripleEqual => (
699 "Did you mean `==`?",
700 vec![
701 "Gleam uses `==` to check for equality between two values.".into(),
702 "See: https://tour.gleam.run/basics/equality".into(),
703 ],
704 ),
705 }
706 }
707}