//! Parser for Fenl expressions.
//!
//! This module uses [[logos]] to generate the lexer and [lalrpop](https://crates.io/crates/lalrpop)
//! to generate the parser.

use lalrpop_util::lalrpop_mod;

use crate::{ExprRef, FeatureSetPart, FenlType, Signature};

lalrpop_mod!(
    #[allow(clippy::all)]
    #[allow(unused)]
    #[allow(explicit_outlives_requirements)]
    grammar,
    "/parser/grammar.rs" // generated by LALRPOP
);

mod lexer;
mod token;

pub use token::Token;

#[cfg(test)]
mod tests;

/// Return true if the input is a valid identifier.
pub fn is_valid_ident(ident: &str) -> bool {
    let mut lexer = lexer::Lexer::new(ident);
    match lexer.next() {
        Some(Ok((_, Token::Ident(_), _))) => lexer.next().is_none(),
        None | Some(_) => false,
    }
}

pub type ParseError<'input> =
    lalrpop_util::ParseError<usize, Token<'input>, (usize, String, usize)>;
pub type ParseErrors<'input> = Vec<ParseError<'input>>;

pub(crate) fn try_parse_expr(
    part_id: FeatureSetPart,
    input: &str,
) -> Result<ExprRef, ParseErrors<'_>> {
    try_parse(input, |errors, lexer| {
        grammar::ExprRefParser::new().parse(part_id, errors, lexer)
    })
}

pub(crate) fn try_parse_signature(
    part_id: FeatureSetPart,
    input: &str,
) -> Result<Signature, ParseErrors<'_>> {
    try_parse(input, |errors, lexer| {
        grammar::SignatureParser::new().parse(part_id, errors, lexer)
    })
}

#[cfg(test)]
pub(crate) fn try_parse_arguments(
    input: &'static str,
) -> Result<crate::Arguments<ExprRef>, ParseErrors<'_>> {
    try_parse(input, |errors, lexer| {
        grammar::ArgsParser::new().parse(FeatureSetPart::Internal(input), errors, lexer)
    })
}

pub(crate) fn try_parse_type(
    part_id: FeatureSetPart,
    input: &str,
) -> Result<FenlType, ParseErrors<'_>> {
    try_parse(input, |errors, lexer| {
        grammar::TypeParser::new().parse(part_id, errors, lexer)
    })
}
#[inline]
fn try_parse<T>(
    input: &str,
    parser: impl for<'a> FnOnce(&mut ParseErrors<'a>, lexer::Lexer<'a>) -> Result<T, ParseError<'a>>,
) -> Result<T, ParseErrors<'_>> {
    let lexer = lexer::Lexer::new(input);

    // We *could* pass the diagnostic reporter to the grammar and have it
    // report diagnostics directly. That seemed a bit more finicky, and also
    // is difficult due to package dependencies.
    // Instead, we collect the error recovery entries in this vector, and then
    // add them to the diagnostic.
    let mut errors = Vec::new();
    let result = parser(&mut errors, lexer);

    // Oddly, the grammar may return a top level error, even though other (internal)
    // errors have been added to `errors`. Thus, we need to check the result and
    // add it if needed.
    match result {
        Err(error) => {
            errors.push(error);
            Err(errors)
        }
        Ok(result) if errors.is_empty() => Ok(result),
        Ok(_) => Err(errors),
    }
}
