glue 0.8.7

Glue is a parser combinator framework for parsing text based formats, it is easy to use and relatively fast too.
Documentation
//! Parser combinators for mapping one thing to another.

use crate::types::*;

#[inline]
/// Run a parser and map the error it returns on failure.
///
/// ```
/// # use glue::prelude::*;
/// assert_eq!(
///     map_error(take(1.., is(numeric)), |error| error.tag("a number")).parse("abc"),
///     Err((
///         ParserContext {
///             input: "abc",
///             bounds: 0..0,
///         },
///         SourceError::TaggedRanges(
///             "a number".into(),
///             vec![SourceError::Ranges(vec![SourceError::Range(0..0)])]
///         )
///     ))
/// );
/// ```
pub fn map_error<'a, Res, Par, Map>(mut parser: Par, mut mapper: Map) -> impl Parser<'a, Res>
where
    Par: Parser<'a, Res>,
    Map: FnMut(SourceError) -> SourceError,
{
    move |ctx| parser.parse(ctx).map_error(|result| mapper(result))
}

#[inline]
/// Run a parser and map the data it returns on success.
///
/// ```
/// # use glue::prelude::*;
/// assert_eq!(
///     map_result(take(1.., is(numeric)), |num: &str| num
///         .parse::<usize>()
///         .unwrap())
///     .parse("123"),
///     Ok((
///         ParserContext {
///             input: "123",
///             bounds: 0..3,
///         },
///         123
///     ))
/// );
/// ```
pub fn map_result<'a, OldRes, NewRes, Par, Map>(
    mut parser: Par,
    mut mapper: Map,
) -> impl Parser<'a, NewRes>
where
    Par: Parser<'a, OldRes>,
    Map: FnMut(OldRes) -> NewRes,
{
    move |ctx| parser.parse(ctx).map_result(|result| mapper(result))
}

#[inline]
/// Run a parser and map its result to an `Option<T>`.
///
/// ```
/// # use glue::prelude::*;
/// assert_eq!(
///     optional(is("foobar")).parse("foobar"),
///     Ok((
///         ParserContext {
///             input: "foobar",
///             bounds: 0..6
///         },
///         Some("foobar")
///     ))
/// );
/// assert_eq!(
///     optional(is("foobar")).parse("boofar"),
///     Ok((
///         ParserContext {
///             input: "boofar",
///             bounds: 0..0
///         },
///         None
///     ))
/// );
/// ```
pub fn optional<'a, Res, Par>(mut parser: Par) -> impl Parser<'a, Option<Res>>
where
    Par: Parser<'a, Res>,
{
    move |ctx: ParserContext<'a>| {
        parser
            .parse(ctx.clone())
            .and_then(|(ctx, res)| Ok((ctx, Some(res))))
            .or_else(|(_, _err)| Ok((ctx, None)))
    }
}

#[inline]
pub fn tag_error<'a, Par, Res>(mut parser: Par, message: &'a str) -> impl Parser<'a, Res>
where
    Par: Parser<'a, Res>,
{
    move |ctx| {
        parser
            .parse(ctx)
            .map_error(|error| SourceError::TaggedRanges(message.into(), vec![error]))
    }
}

#[inline]
pub fn tag_error_with<'a, Par, ParRes, Alt, AltRes, Call>(
    mut parser: Par,
    mut alternative: Alt,
    callback: Call,
) -> impl Parser<'a, ParRes>
where
    Par: Parser<'a, ParRes>,
    Alt: Parser<'a, AltRes>,
    Call: Fn(AltRes) -> String,
{
    move |ctx| {
        parser
            .parse(ctx)
            .or_else(|(ctx, error)| match alternative.parse(ctx) {
                Ok((ctx, res)) => Err((
                    ctx.clone(),
                    SourceError::TaggedRanges(
                        callback(res),
                        vec![error, SourceError::Range(ctx.bounds)],
                    ),
                )),
                Err((ctx, _)) => Err((ctx, error)),
            })
    }
}