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 matching and taking strings.

use crate::types::*;
use std::ops::Bound;
use std::ops::RangeBounds;

#[inline]
/// Run a parser a minimum number of times and up to a maximum and capture
/// the input it parsed.
///
/// ```
/// # use glue::prelude::*;
/// assert_eq!(
///     take(1..2, is("foo")).parse("foofoobar"),
///     Ok((
///         ParserContext {
///             input: "foofoobar",
///             bounds: 0..6,
///         },
///         "foofoo"
///     ))
/// );
/// ```
pub fn take<'a, Skip, Par, Rep>(range: Rep, mut callback: Par) -> impl Parser<'a, &'a str>
where
    Par: Parser<'a, Skip>,
    Rep: RangeBounds<usize>,
{
    move |ctx: ParserContext<'a>| {
        let minimum: usize = match range.start_bound() {
            Bound::Excluded(val) => *val,
            Bound::Included(val) => *val,
            Bound::Unbounded => 0,
        };
        let maximum: usize = match range.end_bound() {
            Bound::Excluded(val) => *val,
            Bound::Included(val) => *val + 1,
            Bound::Unbounded => 0,
        };
        let mut temp_ctx = ctx.clone();
        let mut results = 0;

        loop {
            if maximum > 0 && results == maximum {
                break;
            }

            match callback.parse(temp_ctx) {
                Ok((ret_ctx, _)) => {
                    temp_ctx = ret_ctx;
                    results += 1;
                }
                Err((ret_ctx, error)) => {
                    temp_ctx = ret_ctx;

                    if results < minimum {
                        return Err(temp_ctx.with_data(SourceError::Ranges(vec![error])));
                    }

                    break;
                }
            }
        }

        Ok(ctx.expand(temp_ctx).with_str())
    }
}

#[inline]
/// Run each of the provided parsers and in the specified order and return all of the matched input.
///
/// ```
/// # use glue::prelude::*;
/// assert_eq!(
///     take_all((is("foo"), is("bar"))).parse("foobar"),
///     Ok((
///         ParserContext {
///             input: "foobar",
///             bounds: 0..6,
///         },
///         "foobar"
///     ))
/// );
/// ```
pub fn take_all<'a, Res, Par>(mut parser: Par) -> impl Parser<'a, &'a str>
where
    Par: SequenceParser<'a, Res>,
{
    move |ctx| {
        parser
            .take_all(ctx)
            .map_error(|error| SourceError::Ranges(vec![error]))
    }
}

#[inline]
/// Run each of the provided parsers until one is successful and return the input it parsed.
///
/// ```
/// # use glue::prelude::*;
/// assert_eq!(
///     take_any((is("foo"), is("bar"))).parse("bar"),
///     Ok((
///         ParserContext {
///             input: "bar",
///             bounds: 0..3,
///         },
///         "bar"
///     ))
/// );
/// ```
///
/// Each parser that is passed to `take_any` must return the same type as the parser only has a single output type.
pub fn take_any<'a, Res, Par>(mut parser: Par) -> impl Parser<'a, &'a str>
where
    Par: BranchParser<'a, Res>,
{
    move |ctx| parser.take_any(ctx)
}

#[inline]
/// Run a parser until a predicate is reached and capture the input it parsed.
///
/// ```
/// # use glue::prelude::*;
/// assert_eq!(
///     take_until(eoi(), is(digit)).parse("123"),
///     Ok((
///         ParserContext {
///             input: "123",
///             bounds: 0..3,
///         },
///         "123"
///     ))
/// );
/// ```
pub fn take_until<'a, Skip, Res, Pred, Par>(
    mut predicate: Pred,
    mut callback: Par,
) -> impl Parser<'a, &'a str>
where
    Pred: Parser<'a, Skip>,
    Par: Parser<'a, Res>,
{
    move |ctx: ParserContext<'a>| {
        let mut temp_ctx = ctx.clone();

        loop {
            match predicate.parse(temp_ctx.clone()) {
                Ok((_, _)) => {
                    break;
                }
                Err((_, _)) => {}
            }

            match callback.parse(temp_ctx) {
                Ok((ret_ctx, _)) => {
                    temp_ctx = ret_ctx;
                }
                Err((ret_ctx, error)) => {
                    return Err(ret_ctx.with_data(SourceError::Ranges(vec![error])));
                }
            }
        }

        Ok(ctx.expand(temp_ctx).with_str())
    }
}