Documentation
use std::ops::Range;

use thiserror::Error;

#[derive(Debug, PartialEq, Error)]
#[error("parse error at {start_pos}..{end_pos}: {kind}")]
pub struct ParseError {
    pub start_pos: usize,
    pub end_pos: usize,
    pub kind: ParseErrorKind,
}

impl ParseError {
    pub fn span(&self) -> Range<usize> {
        self.start_pos..self.end_pos
    }
}

#[derive(Debug, PartialEq, Error)]
pub enum ParseErrorKind {
    #[error("invalid token")]
    Lex,
    #[error("unexpected token, expecting: {0}")]
    UnexpectedToken(String),
    #[error("unexpected EOF, expecting: {0}")]
    UnexpectedEof(String),
    #[error("extra tokens")]
    ExtraToken,
    #[error("too many dereferences")]
    TooManyDeref,

    #[error("invalid redirection port")]
    InvalidRedirectPort,

    #[error("invalid '{0}' at this location")]
    Validation(&'static str),
}

impl ParseError {
    pub fn new(start_pos: usize, end_pos: usize, kind: ParseErrorKind) -> Self {
        Self {
            start_pos,
            end_pos,
            kind,
        }
    }
}

impl<T> From<lalrpop_util::ParseError<usize, T, ParseError>> for ParseError {
    fn from(err: lalrpop_util::ParseError<usize, T, ParseError>) -> Self {
        use lalrpop_util::ParseError as E;

        let (start_pos, end_pos, kind) = match err {
            E::UnrecognizedEof { location, expected } => (
                location,
                location,
                ParseErrorKind::UnexpectedEof(expected.join(", ")),
            ),
            E::UnrecognizedToken { token, expected } => (
                token.0,
                token.2,
                ParseErrorKind::UnexpectedToken(expected.join(", ")),
            ),
            E::ExtraToken { token } => (token.0, token.2, ParseErrorKind::ExtraToken),
            E::User { error } => return error,
            E::InvalidToken { .. } => unreachable!(),
        };

        Self::new(start_pos, end_pos, kind)
    }
}