marser 0.1.2

Parser combinator toolkit with matcher-level backtracking and rich error reporting.
Documentation
//! Predicate-based token parser: `check_fn` gates consumption; `parse_fn` maps the token to output.

use crate::{
    context::ParserContext,
    error::{MatcherRunError, error_handler::ErrorHandler},
    input::{Input, InputStream},
    parser::ParserCombinator,
};

/// [`crate::parser::Parser`] built from a predicate and a projection function.
#[derive(Clone)]
pub struct TokenParser<CheckF, ParseF> {
    check_fn: CheckF,
    parse_fn: ParseF,
}

impl<CheckF, ParseF> ParserCombinator for TokenParser<CheckF, ParseF>
where
    CheckF: Clone,
    ParseF: Clone,
{
}

impl<CheckF, ParseF> std::fmt::Debug for TokenParser<CheckF, ParseF> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("TokenParser").finish()
    }
}

impl<CheckF, ParseF> TokenParser<CheckF, ParseF> {
    /// See [`token_parser`].
    pub fn new<Token, Out>(check_fn: CheckF, parse_fn: ParseF) -> Self
    where
        CheckF: Fn(&Token) -> bool,
        ParseF: Fn(&Token) -> Out,
    {
        Self { check_fn, parse_fn }
    }
}

/// Convenience constructor for [`TokenParser`].
pub fn token_parser<CheckF, ParseF, Token, Out>(
    check_fn: CheckF,
    parse_fn: ParseF,
) -> TokenParser<CheckF, ParseF>
where
    CheckF: Fn(&Token) -> bool,
    ParseF: Fn(&Token) -> Out,
{
    TokenParser::new(check_fn, parse_fn)
}

impl<'src, Inp: Input<'src>, Out, CheckF, ParseF> super::internal::ParserImpl<'src, Inp>
    for TokenParser<CheckF, ParseF>
where
    CheckF: Fn(&<Inp as Input<'src>>::Token) -> bool + Clone,
    ParseF: Fn(&<Inp as Input<'src>>::Token) -> Out + Clone,
{
    type Output = Out;
    const CAN_FAIL: bool = true;

    #[inline]
    fn parse(
        &self,
        _context: &mut ParserContext<'src>,
        _error_handler: &mut impl ErrorHandler,
        input: &mut InputStream<'src, Inp>,
    ) -> Result<Option<Self::Output>, MatcherRunError> {
        let start = input.get_pos();
        let Some(token) = input.next() else {
            return Ok(None);
        };
        if (self.check_fn)(&token) {
            return Ok(Some((self.parse_fn)(&token)));
        }
        input.set_pos(start);
        Ok(None)
    }
}