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