// SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2020 The Gleam contributors use crate::ast::{RecordConstructorArg, SrcSpan, TypeAst}; use crate::diagnostic::{ExtraLabel, Label}; use crate::error::{wrap, wrap_format}; use crate::parse::Token; use ecow::EcoString; use itertools::Itertools; #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct LexicalError { pub error: LexicalErrorType, pub location: SrcSpan, } #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum InvalidUnicodeEscapeError { /// Expected '{' MissingOpeningBrace, /// Expected hex digit or '}' ExpectedHexDigitOrCloseBrace, /// Expected between 1 and 6 hex digits InvalidNumberOfHexDigits, /// Invalid Unicode codepoint InvalidCodepoint, } #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum LexicalErrorType { // String contains an unescaped slash BadStringEscape, // \u{...} escape sequence is invalid InvalidUnicodeEscape(InvalidUnicodeEscapeError), // 0x012 , 2 is out of radix DigitOutOfRadix, // 1_000_ is not allowed NumTrailingUnderscore, // 0x, 0b, 0o without a value RadixIntNoValue, // // 1.0e, for example, where there is no exponent MissingExponent, // Unterminated string literal UnexpectedStringEnd, UnrecognizedToken { tok: char, }, InvalidTripleEqual, /// A character was encountered that visually looks like a correct /// character, but in reality it's some other unicode characters. /// For example, a non-breaking-space instead of a regular space. VisuallySimilarInvalidCharacter { name: &'static str, correct: &'static str, }, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct ParseError { pub error: ParseErrorType, pub location: SrcSpan, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] /// Where we found an incorrect name. pub enum IncorrectNamePosition { /// After `as`. For example: `as _wibble`. AsPattern, /// As a module in an import: `import _wibble` Module, /// As a function name: `fn _wibble()` Function, /// As an attribute name: `@_wibble` Attribute, /// As a constant name: `const _wibble = ...` Constant, /// As a target name: `@external(_wibble, ...)` Target, /// Used as a variable expression: `let a = _wibble`, `let a = _wibble(10)` Variable, } #[derive(Debug, Clone, PartialEq, Eq)] pub enum ParseErrorType { ExpectedEqual, // expect "=" ExpectedExpr, // after "->" in a case clause ExpectedName, // any token used when a Name was expected ExpectedPattern, // after ':' where a pattern is expected ExpectedType, // after ':' or '->' where a type annotation is expected ExpectedUpName, // any token used when a UpName was expected ExpectedValue, // no value after "=" ExpectedDefinition, // after attributes ExpectedDeprecationMessage, // after "deprecated" ExpectedExternalArguments, // after "@external" ExpectedFunctionDefinition, // after function-only attributes ExpectedTargetName, // after "@target(" ExprLparStart, // it seems "(" was used to start an expression ExtraSeparator, // #(1,,) <- the 2nd comma is an extra separator // UpName or DiscardName used when Name was expected IncorrectName { // Where we found the incorrect name. kind: IncorrectNamePosition, }, IncorrectUpName, // Name or DiscardName used when UpName was expected InvalidBitArraySegment, // <<7:hello>> `hello` is an invalid BitArray segment InvalidBitArrayUnit, // in <<1:unit(x)>> x must be 1 <= x <= 256 InvalidTailPattern, // only name and _name are allowed after ".." in list pattern InvalidTupleAccess, // only positive int literals for tuple access LexError { error: LexicalError, }, NestedBitArrayPattern, // <<<<1>>, 2>>, <<1>> is not allowed in there NoLetBinding, // Bindings and rebinds always require let and must always bind to a value. NoValueAfterEqual, // = NotConstType, // :fn(), name, _ are not valid const types OpNakedRight, // Operator with no value to the right OpaqueTypeAlias, // Type aliases cannot be opaque TooManyArgHoles, // a function call can have at most 1 arg hole DuplicateAttribute, // an attribute was used more than once UnknownAttribute, // an attribute was used that is not known UnknownTarget, // an unknown target was used ListSpreadWithoutElements, // Pointless spread: `[..xs]` ListSpreadFollowedByElements, // trying to append something after the spread: `[..xs, x]` ListSpreadWithAnotherSpread { first_spread_location: SrcSpan, }, // trying to use multiple spreads: `[..xs, ..ys]` UnexpectedLabel, // argument labels were provided, but are not supported in this context UnexpectedEof, UnexpectedReservedWord, // reserved word used when a name was expected UnexpectedToken { token: Token, expected: Vec, hint: Option, }, UnexpectedFunction, // a function was used called outside of another function /// A variable was assigned or discarded on the left hand side of a <> pattern ConcatPatternVariableLeftHandSide, /// A variable was assigned as infix between prefix and suffix string /// patterns using <> ConcatPatternVariableWithSuffix { name: EcoString, }, ListSpreadWithoutTail, // let x = [1, ..] ExpectedFunctionBody, // let x = fn() RedundantInternalAttribute, // for a private definition marked as internal InvalidModuleTypePattern, // for patterns that have a dot like: `name.thing` ListPatternSpreadFollowedByElements, // When there is a pattern after a spread [..rest, pattern] /// This happens when someone forgets to write the type constructor around /// its fields in a custom type definition. For example: /// /// ```gleam /// pub type Wibble { /// String, /// field: Int, /// field_2: a, /// } /// ``` ExpectedRecordConstructor { type_name: EcoString, public: bool, opaque: bool, /// Those are the fields that have been written. fields: Vec>, }, CallInClauseGuard, // case x { _ if f() -> 1 } IfExpression, TypeDefinitionNoArguments, // pub type Wibble() { ... } UnknownAttributeRecordVariant, // an attribute was used that is not know for a custom type variant // a Python-like import was written, such as `import gleam.io`, instead of `import gleam/io` IncorrectImportModuleSeparator { module: EcoString, item: EcoString, }, /// This can happen when there's an empty block in a case clause guard. /// For example: `_ if a == {}` EmptyGuardBlock, // When the use tries to define a constant inside a function ConstantInsideFunction, FunctionDefinitionAngleGenerics, // fn something() { ... } // let a: List = [] TypeUsageAngleGenerics { module: Option, name: EcoString, arguments: Vec, }, // type Something { TypeDefinitionAngleGenerics { name: EcoString, arguments: Vec, }, // `const x = todo as` with no message after the `as`. MissingConstantAsMessage, } pub(crate) struct ParseErrorDetails { pub text: String, pub label_text: EcoString, pub extra_labels: Vec, pub hint: Option, } impl ParseErrorType { pub(crate) fn details(&self) -> ParseErrorDetails { match self { ParseErrorType::ExpectedEqual => ParseErrorDetails { text: "".into(), hint: None, label_text: "I was expecting a '=' after this".into(), extra_labels: vec![], }, ParseErrorType::ExpectedExpr => ParseErrorDetails { text: "".into(), hint: None, label_text: "I was expecting an expression after this".into(), extra_labels: vec![], }, ParseErrorType::ExpectedName => ParseErrorDetails { text: "".into(), hint: None, label_text: "I was expecting a name here".into(), extra_labels: vec![], }, ParseErrorType::ExpectedPattern => ParseErrorDetails { text: "".into(), hint: None, label_text: "I was expecting a pattern after this".into(), extra_labels: vec![], }, ParseErrorType::ExpectedType => ParseErrorDetails { text: "See: https://tour.gleam.run/basics/assignments/".into(), hint: None, label_text: "I was expecting a type after this".into(), extra_labels: vec![], }, ParseErrorType::ExpectedUpName => ParseErrorDetails { text: "".into(), hint: None, label_text: "I was expecting a type name here".into(), extra_labels: vec![], }, ParseErrorType::ExpectedValue => ParseErrorDetails { text: "".into(), hint: None, label_text: "I was expecting a value after this".into(), extra_labels: vec![], }, ParseErrorType::ExpectedDefinition => ParseErrorDetails { text: "".into(), hint: None, label_text: "I was expecting a definition after this".into(), extra_labels: vec![], }, ParseErrorType::ExpectedDeprecationMessage => ParseErrorDetails { text: "See: https://tour.gleam.run/functions/deprecations/".into(), hint: None, label_text: "A deprecation attribute must have a string message.".into(), extra_labels: vec![], }, ParseErrorType::ExpectedExternalArguments => ParseErrorDetails { text: "".into(), hint: Some("See https://tour.gleam.run/advanced-features/externals/".into()), label_text: "This attribute is incomplete".into(), extra_labels: vec![], }, ParseErrorType::ExpectedFunctionDefinition => ParseErrorDetails { text: "".into(), hint: None, label_text: "I was expecting a function definition after this".into(), extra_labels: vec![], }, ParseErrorType::ExpectedTargetName => ParseErrorDetails { text: "Try `erlang`, `javascript`.".into(), hint: None, label_text: "I was expecting a target name after this".into(), extra_labels: vec![], }, ParseErrorType::ExtraSeparator => ParseErrorDetails { text: "".into(), hint: Some("Try removing it?".into()), label_text: "This is an extra delimiter".into(), extra_labels: vec![], }, ParseErrorType::ExprLparStart => ParseErrorDetails { text: "".into(), hint: Some( "To group expressions in Gleam, use \"{\" and \"}\"; \ tuples are created with `#(` and `)`." .into(), ), label_text: "This parenthesis cannot be understood here".into(), extra_labels: vec![], }, ParseErrorType::IncorrectName { kind } => { let subject = match kind { IncorrectNamePosition::AsPattern => "A name after `as`", IncorrectNamePosition::Module => "A module name", IncorrectNamePosition::Function => "A function name", IncorrectNamePosition::Attribute => "An attribute name", IncorrectNamePosition::Constant => "A constant name", IncorrectNamePosition::Target => "A target name", IncorrectNamePosition::Variable => "A variable name", }; ParseErrorDetails { text: wrap_format!( "{subject} must start with a lowercase letter, and can \ contain a-z, 0-9, or _.", ), hint: None, label_text: "I'm expecting a lowercase name here".into(), extra_labels: vec![], } } ParseErrorType::IncorrectUpName => ParseErrorDetails { text: "".into(), hint: Some(wrap( "Type names start with a uppercase letter, and can \ contain a-z, A-Z, or 0-9.", )), label_text: "I'm expecting a type name here".into(), extra_labels: vec![], }, ParseErrorType::InvalidBitArraySegment => ParseErrorDetails { text: "See: https://tour.gleam.run/data-types/bit-arrays/".into(), hint: Some(format!( "Valid BitArray segment options are:\n{}", wrap( "bits, bytes, int, float, utf8, utf16, utf32, utf8_codepoint, \ utf16_codepoint, utf32_codepoint, signed, unsigned, big, little, native, size, unit.", ) )), label_text: "This is not a valid BitArray segment option".into(), extra_labels: vec![], }, ParseErrorType::InvalidBitArrayUnit => ParseErrorDetails { text: "See: https://tour.gleam.run/data-types/bit-arrays/".into(), hint: Some("Unit must be an integer literal >= 1 and <= 256.".into()), label_text: "This is not a valid BitArray unit value".into(), extra_labels: vec![], }, ParseErrorType::InvalidTailPattern => ParseErrorDetails { text: "".into(), hint: None, label_text: "This part of a list pattern can only be a name or a discard".into(), extra_labels: vec![], }, ParseErrorType::InvalidTupleAccess => ParseErrorDetails { text: "".into(), hint: Some( "Only non negative integer literals like 0, or 1_000 can be used.".into(), ), label_text: "This integer is not valid for tuple access".into(), extra_labels: vec![], }, ParseErrorType::LexError { error: lex_err } => { let (label_text, text_lines) = lex_err.to_parse_error_info(); let text = text_lines.join("\n"); ParseErrorDetails { text, hint: None, label_text: label_text.into(), extra_labels: vec![], } } ParseErrorType::NestedBitArrayPattern => ParseErrorDetails { text: "".into(), hint: None, label_text: "BitArray patterns cannot be nested".into(), extra_labels: vec![], }, ParseErrorType::NotConstType => ParseErrorDetails { text: "See: https://tour.gleam.run/basics/constants/".into(), hint: None, label_text: "This type is not allowed in module constants".into(), extra_labels: vec![], }, ParseErrorType::NoLetBinding => ParseErrorDetails { text: "See: https://tour.gleam.run/basics/assignments/".into(), hint: Some("Use let for binding.".into()), label_text: "There must be a 'let' to bind variable to value".into(), extra_labels: vec![], }, ParseErrorType::NoValueAfterEqual => ParseErrorDetails { text: "".into(), hint: None, label_text: "I was expecting to see a value after this equals sign".into(), extra_labels: vec![], }, ParseErrorType::OpaqueTypeAlias => ParseErrorDetails { text: "See: https://tour.gleam.run/basics/type-aliases/".into(), hint: None, label_text: "Type Aliases cannot be opaque".into(), extra_labels: vec![], }, ParseErrorType::OpNakedRight => ParseErrorDetails { text: "".into(), hint: Some("Remove it or put a value after it.".into()), label_text: "This operator has no value on its right side".into(), extra_labels: vec![], }, ParseErrorType::TooManyArgHoles => ParseErrorDetails { text: "See: https://tour.gleam.run/functions/functions/".into(), hint: Some("Function calls can have at most one argument hole.".into()), label_text: "There is more than 1 argument hole in this function call".into(), extra_labels: vec![], }, ParseErrorType::UnexpectedEof => ParseErrorDetails { text: "".into(), hint: None, label_text: "The module ended unexpectedly".into(), extra_labels: vec![], }, ParseErrorType::ListSpreadWithoutElements => ParseErrorDetails { text: "See: https://tour.gleam.run/basics/lists/".into(), hint: Some("Try prepending some elements [1, 2, ..list].".into()), label_text: "This spread does nothing".into(), extra_labels: vec![], }, ParseErrorType::ListSpreadWithAnotherSpread { first_spread_location, } => ParseErrorDetails { text: [ "Lists are immutable and singly-linked, so to join two or more lists", "all the elements of the lists would need to be copied into a new list.", "This would be slow, so there is no built-in syntax for it.", ] .join("\n"), hint: None, label_text: "I wasn't expecting a second list here".into(), extra_labels: vec![ExtraLabel { src_info: None, label: Label { text: Some("You're using a list here".into()), span: *first_spread_location, }, }], }, ParseErrorType::ListSpreadFollowedByElements => ParseErrorDetails { text: [ "Lists are immutable and singly-linked, so to append items to them", "all the elements of a list would need to be copied into a new list.", "This would be slow, so there is no built-in syntax for it.", ] .join("\n"), hint: Some( "Prepend items to the list and then reverse it once you are done.".into(), ), label_text: "I wasn't expecting elements after this".into(), extra_labels: vec![], }, ParseErrorType::ListPatternSpreadFollowedByElements => ParseErrorDetails { text: [ "Lists are immutable and singly-linked, so to match on the end", "of a list would require the whole list to be traversed. This", "would be slow, so there is no built-in syntax for it. Pattern", "match on the start of the list instead.", ] .join("\n"), hint: None, label_text: "I wasn't expecting elements after this".into(), extra_labels: vec![], }, ParseErrorType::UnexpectedReservedWord => ParseErrorDetails { text: "".into(), hint: Some("I was expecting to see a name here.".into()), label_text: "This is a reserved word".into(), extra_labels: vec![], }, ParseErrorType::UnexpectedLabel => ParseErrorDetails { text: "Please remove the argument label.".into(), hint: None, label_text: "Argument labels are not allowed for anonymous functions".into(), extra_labels: vec![], }, ParseErrorType::UnexpectedToken { token, expected, hint, } => { let found = match token { Token::Int { .. } => "an Int".to_string(), Token::Float { .. } => "a Float".to_string(), Token::String { .. } => "a String".to_string(), Token::CommentDoc { .. } => "a comment".to_string(), Token::DiscardName { .. } => "a discard name".to_string(), Token::Name { .. } | Token::UpName { .. } => "a name".to_string(), _ if token.is_reserved_word() => format!("the keyword {token}"), Token::LeftParen | Token::RightParen | Token::LeftSquare | Token::RightSquare | Token::LeftBrace | Token::RightBrace | Token::Plus | Token::Minus | Token::Star | Token::Slash | Token::Less | Token::Greater | Token::LessEqual | Token::GreaterEqual | Token::Percent | Token::PlusDot | Token::MinusDot | Token::StarDot | Token::SlashDot | Token::LessDot | Token::GreaterDot | Token::LessEqualDot | Token::GreaterEqualDot | Token::Concatenate | Token::Colon | Token::Comma | Token::Hash | Token::Bang | Token::Equal | Token::EqualEqual | Token::NotEqual | Token::Vbar | Token::VbarVbar | Token::AmperAmper | Token::LtLt | Token::GtGt | Token::Pipe | Token::Dot | Token::RArrow | Token::LArrow | Token::DotDot | Token::At | Token::EndOfFile | Token::CommentNormal | Token::CommentModule | Token::NewLine | Token::As | Token::Assert | Token::Auto | Token::Case | Token::Const | Token::Delegate | Token::Derive | Token::Echo | Token::Else | Token::Fn | Token::If | Token::Implement | Token::Import | Token::Let | Token::Macro | Token::Opaque | Token::Panic | Token::Pub | Token::Test | Token::Todo | Token::Type | Token::Use => token.to_string(), }; let mut messages = std::iter::once(format!("Found {found}, expected one of: ")) .chain(expected.iter().map(|s| format!("- {s}"))); ParseErrorDetails { text: messages.join("\n"), hint: hint.as_ref().map(|hint| hint.to_string()), label_text: "I was not expecting this".into(), extra_labels: vec![], } } ParseErrorType::ConcatPatternVariableLeftHandSide => ParseErrorDetails { text: [ "We can't tell what size this prefix should be so we don't know", "how to handle this pattern.", "", "If you want to match one character consider using `pop_grapheme`", "from the stdlib's `gleam/string` module.", ] .join("\n"), hint: None, label_text: "This must be a string literal".into(), extra_labels: vec![], }, ParseErrorType::ConcatPatternVariableWithSuffix { name } => ParseErrorDetails { text: [ "A string pattern can only match on a literal string prefix.", "", &wrap_format!( "Matching on a literal suffix is not possible, because `{name}` \ would have an unknown size." ), ] .join("\n"), hint: None, label_text: "This pattern is not allowed".into(), extra_labels: vec![], }, ParseErrorType::UnexpectedFunction => ParseErrorDetails { text: "".into(), hint: None, label_text: "Functions can only be called within other functions".into(), extra_labels: vec![], }, ParseErrorType::ListSpreadWithoutTail => ParseErrorDetails { text: "If a list expression has a spread then a tail must also be given.".into(), hint: None, label_text: "I was expecting a value after this spread".into(), extra_labels: vec![], }, ParseErrorType::UnknownAttribute => ParseErrorDetails { text: "".into(), hint: Some("Try `deprecated`, `external` or `internal` instead.".into()), label_text: "I don't recognise this attribute".into(), extra_labels: vec![], }, ParseErrorType::DuplicateAttribute => ParseErrorDetails { text: "This attribute has already been given.".into(), hint: None, label_text: "Duplicate attribute".into(), extra_labels: vec![], }, ParseErrorType::UnknownTarget => ParseErrorDetails { text: "Try `erlang`, `javascript`.".into(), hint: None, label_text: "I don't recognise this target".into(), extra_labels: vec![], }, ParseErrorType::ExpectedFunctionBody => ParseErrorDetails { text: "".into(), hint: None, label_text: "This function does not have a body".into(), extra_labels: vec![], }, ParseErrorType::RedundantInternalAttribute => ParseErrorDetails { text: "Only a public definition can be annotated as internal.".into(), hint: Some("Remove the `@internal` annotation.".into()), label_text: "Redundant internal attribute".into(), extra_labels: vec![], }, ParseErrorType::InvalidModuleTypePattern => ParseErrorDetails { text: [ "I'm expecting a pattern here,", "or a variable to bind a value to, etc.", ] .join("\n"), hint: Some( "A pattern can be a constructor name, a literal value See: https://tour.gleam.run/flow-control/case-expressions/" .into(), ), label_text: "Invalid pattern".into(), extra_labels: vec![], }, ParseErrorType::ExpectedRecordConstructor { type_name, public, opaque, fields, } => { let (accessor, opaque) = match *public { true if *opaque => ("pub ", "opaque "), true => ("pub ", ""), false => ("", ""), }; let fields = fields .iter() .map(|field| { let mut type_ = EcoString::new(); field.ast.print(&mut type_); match field.label.as_ref() { Some((_, label)) => format!(" {label}: {type_},"), None => format!(" {type_},"), } }) .join("\n"); ParseErrorDetails { text: format!( "Each custom type variant must have a constructor: {accessor}{opaque}type {type_name} {{ {type_name}( {fields} ) }}" ), hint: None, label_text: "I was not expecting this".into(), extra_labels: vec![], } } ParseErrorType::CallInClauseGuard => ParseErrorDetails { text: "Functions cannot be called in clause guards.".into(), hint: None, label_text: "Unsupported expression".into(), extra_labels: vec![], }, ParseErrorType::IfExpression => ParseErrorDetails { text: [ "If you want to write a conditional expression you can use a `case`:", "", " case condition {", " True -> todo", " False -> todo", " }", "", "See: https://tour.gleam.run/flow-control/case-expressions/", ] .join("\n"), hint: None, label_text: "Gleam doesn't have if expressions".into(), extra_labels: vec![], }, ParseErrorType::TypeDefinitionNoArguments => ParseErrorDetails { text: "A generic type must have at least a generic parameter.".into(), hint: Some("If a type is not generic you should omit the `()`.".into()), label_text: "I was expecting generic parameters here".into(), extra_labels: vec![], }, ParseErrorType::UnknownAttributeRecordVariant => ParseErrorDetails { text: "".into(), hint: Some("Did you mean `@deprecated`?".into()), label_text: "This attribute cannot be used on a variant.".into(), extra_labels: vec![], }, ParseErrorType::IncorrectImportModuleSeparator { module, item } => ParseErrorDetails { text: [ "Perhaps you meant one of:".into(), "".into(), format!(" import {module}/{item}"), format!(" import {module}.{{item}}"), ] .join("\n"), hint: None, label_text: "I was expecting either `/` or `.{` here.".into(), extra_labels: vec![], }, ParseErrorType::EmptyGuardBlock => ParseErrorDetails { text: "".into(), hint: None, label_text: "A clause guard block cannot be empty".into(), extra_labels: vec![], }, ParseErrorType::MissingConstantAsMessage => ParseErrorDetails { text: "".into(), hint: None, label_text: "I was expecting to see a constant expression after this `as`".into(), extra_labels: vec![], }, ParseErrorType::ConstantInsideFunction => ParseErrorDetails { text: wrap( "All variables are immutable in Gleam, so constants inside \ functions are not necessary.", ), hint: Some( "Either move this into the global scope or use `let` binding instead.".into(), ), label_text: "Constants are not allowed inside functions".into(), extra_labels: vec![], }, ParseErrorType::FunctionDefinitionAngleGenerics => ParseErrorDetails { text: "\ Generic function type variables do not need to be predeclared like they would be in some other languages, instead they are written with lowercase names. fn example(argument: generic) -> generic See: https://tour.gleam.run/functions/generic-functions/" .into(), hint: None, label_text: "I was expecting `(` here.".into(), extra_labels: vec![], }, ParseErrorType::TypeUsageAngleGenerics { module, name, arguments, } => { let type_arguments = arguments .iter() .map(|argument| { let mut argument_string = EcoString::new(); argument.print(&mut argument_string); argument_string }) .join(", "); let replacement_type = match module { Some(module) => format!("{module}.{name}({type_arguments})"), None => format!("{name}({type_arguments})"), }; ParseErrorDetails { text: format!( "\ Type parameters use lowercase names and are surrounded by parentheses. {replacement_type} See: https://tour.gleam.run/data-types/generic-custom-types/" ), hint: None, label_text: "I was expecting `(` here.".into(), extra_labels: vec![], } } ParseErrorType::TypeDefinitionAngleGenerics { name, arguments } => { let comma_separated_arguments = arguments.join(", "); ParseErrorDetails { text: format!( "\ Type parameters use lowercase names and are surrounded by parentheses. type {name}({comma_separated_arguments}) {{ See: https://tour.gleam.run/data-types/generic-custom-types/" ), hint: None, label_text: "I was expecting `(` here.".into(), extra_labels: vec![], } } } } } impl LexicalError { pub fn to_parse_error_info(&self) -> (&'static str, Vec) { match &self.error { LexicalErrorType::BadStringEscape => ( "I don't understand this escape code", vec![ "Hint: Add another backslash before it.".into(), "See: https://tour.gleam.run/basics/strings".into(), ], ), LexicalErrorType::DigitOutOfRadix => { ("This digit is too big for the specified radix", vec![]) } LexicalErrorType::NumTrailingUnderscore => ( "Numbers cannot have a trailing underscore", vec!["Hint: remove it.".into()], ), LexicalErrorType::RadixIntNoValue => ("This integer has no value", vec![]), LexicalErrorType::MissingExponent => ( "This float is missing an exponent", vec!["Hint: Add an exponent or remove the trailing `e`".into()], ), LexicalErrorType::UnexpectedStringEnd => { ("The string starting here was left open", vec![]) } LexicalErrorType::UnrecognizedToken { tok } if *tok == ';' => ( "Remove this semicolon", vec![ "Hint: Semicolons used to be whitespace and did nothing.".into(), "You can safely remove them without your program changing.".into(), ], ), LexicalErrorType::UnrecognizedToken { tok } if *tok == '\'' => ( "Unexpected single quote", vec!["Hint: Strings are written with double quotes.".into()], ), LexicalErrorType::UnrecognizedToken { .. } => ( "I can't figure out what to do with this character", vec!["Hint: Is it a typo?".into()], ), LexicalErrorType::InvalidUnicodeEscape( InvalidUnicodeEscapeError::MissingOpeningBrace, ) => ( "Expected '{' in Unicode escape sequence", vec!["Hint: Add it.".into()], ), LexicalErrorType::InvalidUnicodeEscape( InvalidUnicodeEscapeError::ExpectedHexDigitOrCloseBrace, ) => ( "Expected hex digit or '}' in Unicode escape sequence", vec![ "Hint: Hex digits are digits from 0 to 9 and letters from a to f or A to F." .into(), ], ), LexicalErrorType::InvalidUnicodeEscape( InvalidUnicodeEscapeError::InvalidNumberOfHexDigits, ) => ( "Expected between 1 and 6 hex digits in Unicode escape sequence", vec![], ), LexicalErrorType::InvalidUnicodeEscape(InvalidUnicodeEscapeError::InvalidCodepoint) => { ("Invalid Unicode codepoint", vec![]) } LexicalErrorType::InvalidTripleEqual => ( "Did you mean `==`?", vec![ "Gleam uses `==` to check for equality between two values.".into(), "See: https://tour.gleam.run/basics/equality".into(), ], ), LexicalErrorType::VisuallySimilarInvalidCharacter { name, correct } => ( "Unexpected character", vec![wrap(&format!( "This looks like ascii {correct}, but it is actually the unicode {name}." ))], ), } } }