//! Lexer error type.

use ecma_syntax_cat::span::Position;

/// All errors the lexer can produce.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Error {
    /// A character was encountered that does not begin any token.
    UnexpectedChar {
        /// Position of the offending character.
        at: Position,
        /// The character itself.
        ch: char,
    },
    /// A string literal was not terminated before end-of-input or end-of-line.
    UnterminatedString {
        /// Position of the opening quote.
        at: Position,
    },
    /// A block comment (`/* ... */`) was not closed before end-of-input.
    UnterminatedComment {
        /// Position of the opening `/*`.
        at: Position,
    },
    /// A template literal was not terminated before end-of-input.
    UnterminatedTemplate {
        /// Position of the opening backtick.
        at: Position,
    },
    /// A regex literal was not terminated before end-of-line or end-of-input.
    UnterminatedRegex {
        /// Position of the opening `/`.
        at: Position,
    },
    /// A numeric literal had an invalid form (e.g. `0o9`, `1e`, `0x`).
    InvalidNumber {
        /// Position where the invalid number started.
        at: Position,
        /// Short description of the problem.
        reason: &'static str,
    },
    /// A string or template escape sequence was malformed (e.g. `\x`
    /// without two hex digits, `\u{` without a closing brace).
    InvalidEscape {
        /// Position of the `\`.
        at: Position,
        /// Short description of the problem.
        reason: &'static str,
    },
    /// A Unicode escape (`\uNNNN` or `\u{...}`) did not parse as a valid
    /// code point.
    InvalidUnicodeEscape {
        /// Position of the `\`.
        at: Position,
    },
    /// A non-ASCII byte appeared inside an identifier.  v0 of this lexer
    /// is ASCII-only; full Unicode identifier support is a v0.2 TODO.
    NonAsciiInIdentifier {
        /// Position of the offending byte.
        at: Position,
        /// The offending byte.
        byte: u8,
    },
}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::UnexpectedChar { at, ch } => {
                write!(f, "{at}: unexpected character {ch:?}")
            }
            Self::UnterminatedString { at } => {
                write!(f, "{at}: unterminated string literal")
            }
            Self::UnterminatedComment { at } => {
                write!(f, "{at}: unterminated block comment")
            }
            Self::UnterminatedTemplate { at } => {
                write!(f, "{at}: unterminated template literal")
            }
            Self::UnterminatedRegex { at } => {
                write!(f, "{at}: unterminated regex literal")
            }
            Self::InvalidNumber { at, reason } => {
                write!(f, "{at}: invalid numeric literal: {reason}")
            }
            Self::InvalidEscape { at, reason } => {
                write!(f, "{at}: invalid escape sequence: {reason}")
            }
            Self::InvalidUnicodeEscape { at } => {
                write!(f, "{at}: invalid unicode escape")
            }
            Self::NonAsciiInIdentifier { at, byte } => {
                write!(
                    f,
                    "{at}: non-ASCII byte {byte:#04x} in identifier (v0 lexer is ASCII-only)"
                )
            }
        }
    }
}

impl std::error::Error for Error {}
