1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
use crate::{Incomplete, Pipe, Repetition};
use fatal_error::FatalError;
use std::error::Error as StdError;

/// Implemented by [TakeAtom::Atom] to have an atom length
pub trait LenBytes {
    /// number of bytes
    fn len_bytes(&self) -> usize;
}

/// Implemented by types usable with [take_while]
pub trait TakeAtom {
    /// a single element
    type Atom: LenBytes;
    /// a container of [TakeAtom::Atom]
    type Container;

    /// Gets the next [TakeAtom::Atom] and it's index in the container
    fn next(&mut self) -> Option<(usize, Self::Atom)>;

    /// returns a tuple containing `(remaining, consumed)`
    ///  where `consumed` contains all of the items returned by [TakeAtom::next]
    fn split_at(self, index: usize) -> (Self::Container, Self::Container);
}

/// Returns the longest input that matches the predicate
///
/// If quantity condition is not attained an error is returned
#[inline]
pub fn take_while<I, E>(
    mut f: impl FnMut(I::Atom) -> bool,
    qty: impl TryInto<Repetition, Error = impl StdError>,
) -> impl Pipe<I, (I::Container,), E, I::Container>
where
    I: TakeAtom,
    Incomplete: Into<E>,
{
    let qty = qty.try_into().unwrap();
    #[inline]
    move |mut input: I| {
        let mut c = 0;
        let mut idx = 0;
        let mut err = None;
        let mut all = false;
        while !qty.is_max(c) {
            if let Some((i, x)) = input.next() {
                let end = i + x.len_bytes();
                if !f(x) {
                    err = Some(Incomplete::Unknown.into());
                    break;
                }
                idx = end;
                c += 1;
            } else {
                all = true;
                break;
            }
        }
        qty.map_err(c, |_, _, _| {
            if all {
                FatalError::Error(Incomplete::Unknown.into())
            } else {
                FatalError::Error(err.unwrap())
            }
        })?;
        let (r, x) = input.split_at(idx);
        Ok((r, (x,)))
    }
}

/// creates a pipe that takes `qty` of [TakeAtom]
///
/// examples see: [sentences][crate::str::sentences], [words][crate::str::words]
pub fn take_atom<A: TakeAtom, E, E2>(
    qty: impl TryInto<Repetition, Error = E>,
) -> Result<impl Pipe<A, (Vec<A::Atom>,), E2, A::Container>, E>
where
    Incomplete: Into<E2>,
{
    let qty = qty.try_into()?;
    Ok(move |mut input: A| {
        let mut r = Vec::new();
        let mut last = 0;
        for x in 0.. {
            match input.next() {
                Some((i, o)) => {
                    last = i + o.len_bytes();
                    r.push(o);
                }
                None => {
                    if qty.needs_more(x) {
                        return Err(FatalError::Error(Incomplete::Unknown.into()));
                    } else {
                        break;
                    }
                }
            }
            if qty.is_max(x) {
                break;
            }
        }
        Ok((input.split_at(last).0, (r,)))
    })
}