Skip to content

Commit

Permalink
Add while_parser
Browse files Browse the repository at this point in the history
  • Loading branch information
uzytkownik committed Jun 28, 2021
1 parent 9f0b9bd commit ffde67f
Showing 1 changed file with 120 additions and 0 deletions.
120 changes: 120 additions & 0 deletions src/parser/sequence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -891,3 +891,123 @@ where
{
ThenRef(p, f)
}

#[derive(Copy, Clone)]
pub struct While<P, F, S>(Option<P>, F, S);

impl<Input, P, F, S> Parser<Input> for While<P, F, S>
where
Input: Stream,
P: Parser<Input>,
F: FnMut(&mut S, P::Output) -> Option<P>,
S: Clone
{
type Output = S;
type PartialState = (Option<S>, Option<P>, bool, P::PartialState);

parse_mode!(Input);
#[inline]
fn parse_mode_impl<M>(
&mut self,
mut mode: M,
input: &mut Input,
state: &mut Self::PartialState,
) -> ParseResult<Self::Output, <Input as StreamOnce>::Error>
where
M: ParseMode,
{
let While(ref mut init_parser, ref mut func, ref init_state) = *self;
let (ref mut state, ref mut parser, ref mut committed, ref mut partial_state) = *state;
// state == None means we haven't run func yet so we need to depend on init_parser
let mut current_parser_opt: &mut Option<P> = if mode.is_first() || state.is_none() {
debug_assert!(state.is_none());
debug_assert!(parser.is_none());
init_parser
} else {
debug_assert!(state.is_some());
parser
};
while let Some(ref mut current_parser) = *current_parser_opt {
let result = current_parser.parse_mode_impl(mode, input, partial_state);
let value = match result {
CommitOk(next_value) => {
*committed = true;
next_value
},
PeekOk(next_value) => next_value,
CommitErr(e) => return CommitErr(e),
PeekErr(e) => if *committed {
return CommitErr(e.error)
} else {
return PeekErr(e)
}
};
if state.is_none() {
*state = Some(init_state.clone());
}
*parser = func(state.as_mut().unwrap(), value);
current_parser_opt = parser;
mode.set_first();
}
let result = state.as_ref().unwrap_or(init_state).clone();
if *committed {
CommitOk(result)
} else {
PeekOk(result)
}
}
}


// If `init` is not `None` it parses using it first. Than subsequently
// applies `func` to result until `None` is returned. The result is
// the last state.
//
// Otherwise, if `init` is `None`, it returns the `state` without
// consuming any input.
//
/// ```
/// # extern crate combine;
/// # use std::collections::HashMap;
/// # use combine::{Parser, Stream, many1, token, value, unexpected_any, optional, choice};
/// # use combine::parser::char::digit;
/// # use combine::parser::sequence::while_parser;
/// # fn main() {
/// // Parses 'a', 'b' and 'c' such that there is no consecutive letters returning their count
/// #[derive(PartialEq, Eq, Copy, Clone, Hash)]
/// enum Token { A, B, C }
/// fn token_parser<Input>(last_token: Option<Token>) -> impl Parser<Input, Output = Option<Token>>
/// where
/// Input: Stream<Token = char>
/// {
/// let mut choices = vec![];
/// if last_token != Some(Token::A) {
/// choices.push(token('a').map(|_| Token::A).left());
/// }
/// if last_token != Some(Token::B) {
/// choices.push(token('b').map(|_| Token::B).left().right());
/// }
/// if last_token != Some(Token::C) {
/// choices.push(token('c').map(|_| Token::C).right().right());
/// }
/// optional(choice(choices))
/// }
/// let result = while_parser(Some(token_parser(None)), |acc, token_opt| token_opt.map(|token| {
/// *acc.entry(token).or_insert(0) += 1;
/// token_parser(token_opt)
/// }), HashMap::<Token, usize>::new()).parse("ababacbcbcaa");
/// assert_eq!(result.as_ref().map(|x| x.0.get(&Token::A)), Ok(Some(&4)));
/// assert_eq!(result.as_ref().map(|x| x.0.get(&Token::B)), Ok(Some(&4)));
/// assert_eq!(result.as_ref().map(|x| x.0.get(&Token::C)), Ok(Some(&3)));
/// assert_eq!(result.as_ref().map(|x| x.1), Ok("a"));
/// # }
/// ```
pub fn while_parser<Input, P, F, S>(init: Option<P>, func: F, state: S) -> While<P, F, S>
where
Input: Stream,
P: Parser<Input>,
F: FnMut(&mut S, P::Output) -> Option<P>,
S: Clone
{
While(init, func, state)
}

0 comments on commit ffde67f

Please sign in to comment.