diff --git a/examples/json/parser.rs b/examples/json/parser.rs index 9c4ccaa8..250621e9 100644 --- a/examples/json/parser.rs +++ b/examples/json/parser.rs @@ -7,7 +7,7 @@ use winnow::{ combinator::alt, combinator::cut_err, combinator::{delimited, preceded, separated_pair, terminated}, - combinator::{fold_repeat, separated0}, + combinator::{fold_repeat, separated}, error::{AddContext, ParserError}, token::{any, none_of, take, take_while}, }; @@ -153,7 +153,7 @@ fn u16_hex<'i, E: ParserError>>(input: &mut Stream<'i>) -> PResult> + AddContext, &'static str>>( ) -> PResult, E> { preceded( ('[', ws), - cut_err(terminated(separated0(json_value, (ws, ',', ws)), (ws, ']'))), + cut_err(terminated( + separated(0.., json_value, (ws, ',', ws)), + (ws, ']'), + )), ) .context("array") .parse_next(input) @@ -173,7 +176,10 @@ fn object<'i, E: ParserError> + AddContext, &'static str>> ) -> PResult, E> { preceded( ('{', ws), - cut_err(terminated(separated0(key_value, (ws, ',', ws)), (ws, '}'))), + cut_err(terminated( + separated(0.., key_value, (ws, ',', ws)), + (ws, '}'), + )), ) .context("object") .parse_next(input) diff --git a/examples/json/parser_dispatch.rs b/examples/json/parser_dispatch.rs index 6f291556..8f7eaf99 100644 --- a/examples/json/parser_dispatch.rs +++ b/examples/json/parser_dispatch.rs @@ -10,7 +10,7 @@ use winnow::{ combinator::success, combinator::{alt, dispatch}, combinator::{delimited, preceded, separated_pair, terminated}, - combinator::{fold_repeat, separated0}, + combinator::{fold_repeat, separated}, error::{AddContext, ParserError}, token::{any, none_of, take, take_while}, }; @@ -160,7 +160,7 @@ fn u16_hex<'i, E: ParserError>>(input: &mut Stream<'i>) -> PResult> + AddContext, &'static str>>( ) -> PResult, E> { preceded( ('[', ws), - cut_err(terminated(separated0(json_value, (ws, ',', ws)), (ws, ']'))), + cut_err(terminated( + separated(0.., json_value, (ws, ',', ws)), + (ws, ']'), + )), ) .context("array") .parse_next(input) @@ -180,7 +183,10 @@ fn object<'i, E: ParserError> + AddContext, &'static str>> ) -> PResult, E> { preceded( ('{', ws), - cut_err(terminated(separated0(key_value, (ws, ',', ws)), (ws, '}'))), + cut_err(terminated( + separated(0.., key_value, (ws, ',', ws)), + (ws, '}'), + )), ) .context("object") .parse_next(input) diff --git a/examples/json/parser_partial.rs b/examples/json/parser_partial.rs index 1a4bea26..779e05f8 100644 --- a/examples/json/parser_partial.rs +++ b/examples/json/parser_partial.rs @@ -7,7 +7,7 @@ use winnow::{ combinator::alt, combinator::{cut_err, rest}, combinator::{delimited, preceded, separated_pair, terminated}, - combinator::{fold_repeat, separated0}, + combinator::{fold_repeat, separated}, error::{AddContext, ParserError}, stream::Partial, token::{any, none_of, take, take_while}, @@ -154,7 +154,7 @@ fn u16_hex<'i, E: ParserError>>(input: &mut Stream<'i>) -> PResult> + AddContext, &'static str>>( ) -> PResult, E> { preceded( ('[', ws), - cut_err(terminated(separated0(json_value, (ws, ',', ws)), (ws, ']'))), + cut_err(terminated( + separated(0.., json_value, (ws, ',', ws)), + (ws, ']'), + )), ) .context("array") .parse_next(input) @@ -174,7 +177,10 @@ fn object<'i, E: ParserError> + AddContext, &'static str>> ) -> PResult, E> { preceded( ('{', ws), - cut_err(terminated(separated0(key_value, (ws, ',', ws)), (ws, '}'))), + cut_err(terminated( + separated(0.., key_value, (ws, ',', ws)), + (ws, '}'), + )), ) .context("object") .parse_next(input) diff --git a/examples/json_iterator.rs b/examples/json_iterator.rs index b8b46f39..9c21ae38 100644 --- a/examples/json_iterator.rs +++ b/examples/json_iterator.rs @@ -5,7 +5,7 @@ use winnow::{ ascii::{alphanumeric1 as alphanumeric, escaped, float}, combinator::alt, combinator::cut_err, - combinator::separated0, + combinator::separated, combinator::{preceded, separated_pair, terminated}, error::ParserError, error::StrContext, @@ -233,7 +233,7 @@ fn array(i: &mut &str) -> PResult<()> { preceded( '[', cut_err(terminated( - separated0(value, preceded(sp, ',')), + separated(0.., value, preceded(sp, ',')), preceded(sp, ']'), )), ) @@ -249,7 +249,7 @@ fn hash(i: &mut &str) -> PResult<()> { preceded( '{', cut_err(terminated( - separated0(key_value, preceded(sp, ',')), + separated(0.., key_value, preceded(sp, ',')), preceded(sp, '}'), )), ) diff --git a/examples/ndjson/parser.rs b/examples/ndjson/parser.rs index 1fcc9f51..81b47459 100644 --- a/examples/ndjson/parser.rs +++ b/examples/ndjson/parser.rs @@ -8,7 +8,7 @@ use winnow::{ combinator::alt, combinator::cut_err, combinator::{delimited, preceded, separated_pair, terminated}, - combinator::{fold_repeat, separated0}, + combinator::{fold_repeat, separated}, error::{AddContext, ParserError}, stream::Partial, token::{any, none_of, take, take_while}, @@ -158,7 +158,7 @@ fn u16_hex<'i, E: ParserError>>(input: &mut Stream<'i>) -> PResult> + AddContext, &'static str>>( ) -> PResult, E> { preceded( ('[', ws), - cut_err(terminated(separated0(json_value, (ws, ',', ws)), (ws, ']'))), + cut_err(terminated( + separated(0.., json_value, (ws, ',', ws)), + (ws, ']'), + )), ) .context("array") .parse_next(input) @@ -178,7 +181,10 @@ fn object<'i, E: ParserError> + AddContext, &'static str>> ) -> PResult, E> { preceded( ('{', ws), - cut_err(terminated(separated0(key_value, (ws, ',', ws)), (ws, '}'))), + cut_err(terminated( + separated(0.., key_value, (ws, ',', ws)), + (ws, '}'), + )), ) .context("object") .parse_next(input) diff --git a/src/_tutorial/chapter_5.rs b/src/_tutorial/chapter_5.rs index b282c6d2..8aa719b9 100644 --- a/src/_tutorial/chapter_5.rs +++ b/src/_tutorial/chapter_5.rs @@ -137,17 +137,17 @@ //! ``` //! //! You'll notice that the above allows trailing `,` when we intended to not support that. We can -//! easily fix this by using [`separated0`]: +//! easily fix this by using [`separated`]: //! ```rust //! # use winnow::prelude::*; //! # use winnow::token::take_while; //! # use winnow::combinator::dispatch; //! # use winnow::token::take; //! # use winnow::combinator::fail; -//! use winnow::combinator::separated0; +//! use winnow::combinator::separated; //! //! fn parse_list(input: &mut &str) -> PResult> { -//! separated0(parse_digits, ",").parse_next(input) +//! separated(0.., parse_digits, ",").parse_next(input) //! } //! //! // ... @@ -208,14 +208,14 @@ //! # use winnow::combinator::dispatch; //! # use winnow::token::take; //! # use winnow::combinator::fail; -//! # use winnow::combinator::separated0; +//! # use winnow::combinator::separated; //! # //! fn recognize_list<'s>(input: &mut &'s str) -> PResult<&'s str> { //! parse_list.recognize().parse_next(input) //! } //! //! fn parse_list(input: &mut &str) -> PResult<()> { -//! separated0(parse_digits, ",").parse_next(input) +//! separated(0.., parse_digits, ",").parse_next(input) //! } //! //! # fn parse_digits(input: &mut &str) -> PResult { @@ -272,7 +272,7 @@ use super::chapter_2; use super::chapter_3; use crate::combinator; use crate::combinator::repeat; -use crate::combinator::separated0; +use crate::combinator::separated; use crate::stream::Accumulate; use crate::Parser; use std::vec::Vec; diff --git a/src/combinator/mod.rs b/src/combinator/mod.rs index e67843e7..f3e73cb2 100644 --- a/src/combinator/mod.rs +++ b/src/combinator/mod.rs @@ -41,7 +41,7 @@ //! |---|---|---|---|---|---| //! | [`repeat`] | `repeat(1..=3, "ab")` | `"ababc"` | `"c"` | `Ok(vec!["ab", "ab"])` |Applies the parser between m and n times (n included) and returns the list of results in a Vec| //! | [`repeat_till0`] | `repeat_till0(tag( "ab" ), tag( "ef" ))` | `"ababefg"` | `"g"` | `Ok((vec!["ab", "ab"], "ef"))` |Applies the first parser until the second applies. Returns a tuple containing the list of results from the first in a Vec and the result of the second| -//! | [`separated0`] | `separated0("ab", ",")` | `"ab,ab,ab."` | `"."` | `Ok(vec!["ab", "ab", "ab"])` |`separated1` works like `separated0` but must returns at least one element| +//! | [`separated`] | `separated(1..=3, "ab", ",")` | `"ab,ab,ab."` | `"."` | `Ok(vec!["ab", "ab", "ab"])` |Applies the parser and separator between m and n times (n included) and returns the list of results in a Vec| //! | [`fold_repeat`] | `fold_repeat(1..=2, be_u8, \|\| 0, \|acc, item\| acc + item)` | `[1, 2, 3]` | `[3]` | `Ok(3)` |Applies the parser between m and n times (n included) and folds the list of return value| //! //! ## Partial related diff --git a/src/combinator/multi.rs b/src/combinator/multi.rs index 58dd2894..79cabc2f 100644 --- a/src/combinator/multi.rs +++ b/src/combinator/multi.rs @@ -336,6 +336,138 @@ where }) } +/// [`Accumulate`] the output of a parser, interleaved with `sep` +/// +/// This stops when either parser returns [`ErrMode::Backtrack`]. To instead chain an error up, see +/// [`cut_err`][crate::combinator::cut_err]. +/// +/// # Arguments +/// * `m` The minimum number of iterations. +/// * `n` The maximum number of iterations. +/// * `parser` The parser that parses the elements of the list. +/// * `sep` The parser that parses the separator between list elements. +/// +/// # Example +/// +/// Zero or more repetitions: +/// ```rust +/// # #[cfg(feature = "std")] { +/// # use winnow::{error::ErrMode, error::ErrorKind, error::Needed}; +/// # use winnow::prelude::*; +/// use winnow::combinator::separated; +/// use winnow::token::tag; +/// +/// fn parser(s: &str) -> IResult<&str, Vec<&str>> { +/// separated(0.., "abc", "|").parse_peek(s) +/// } +/// +/// assert_eq!(parser("abc|abc|abc"), Ok(("", vec!["abc", "abc", "abc"]))); +/// assert_eq!(parser("abc123abc"), Ok(("123abc", vec!["abc"]))); +/// assert_eq!(parser("abc|def"), Ok(("|def", vec!["abc"]))); +/// assert_eq!(parser(""), Ok(("", vec![]))); +/// assert_eq!(parser("def|abc"), Ok(("def|abc", vec![]))); +/// # } +/// ``` +/// +/// One or more repetitions: +/// ```rust +/// # #[cfg(feature = "std")] { +/// # use winnow::{error::ErrMode, error::{InputError, ErrorKind}, error::Needed}; +/// # use winnow::prelude::*; +/// use winnow::combinator::separated; +/// use winnow::token::tag; +/// +/// fn parser(s: &str) -> IResult<&str, Vec<&str>> { +/// separated(1.., "abc", "|").parse_peek(s) +/// } +/// +/// assert_eq!(parser("abc|abc|abc"), Ok(("", vec!["abc", "abc", "abc"]))); +/// assert_eq!(parser("abc123abc"), Ok(("123abc", vec!["abc"]))); +/// assert_eq!(parser("abc|def"), Ok(("|def", vec!["abc"]))); +/// assert_eq!(parser(""), Err(ErrMode::Backtrack(InputError::new("", ErrorKind::Tag)))); +/// assert_eq!(parser("def|abc"), Err(ErrMode::Backtrack(InputError::new("def|abc", ErrorKind::Tag)))); +/// # } +/// ``` +/// +/// Fixed number of repetitions: +/// ```rust +/// # #[cfg(feature = "std")] { +/// # use winnow::{error::ErrMode, error::{InputError, ErrorKind}, error::Needed}; +/// # use winnow::prelude::*; +/// use winnow::combinator::separated; +/// use winnow::token::tag; +/// +/// fn parser(s: &str) -> IResult<&str, Vec<&str>> { +/// separated(2, "abc", "|").parse_peek(s) +/// } +/// +/// assert_eq!(parser("abc|abc|abc"), Ok(("|abc", vec!["abc", "abc"]))); +/// assert_eq!(parser("abc123abc"), Err(ErrMode::Backtrack(InputError::new("123abc", ErrorKind::Tag)))); +/// assert_eq!(parser("abc|def"), Err(ErrMode::Backtrack(InputError::new("def", ErrorKind::Tag)))); +/// assert_eq!(parser(""), Err(ErrMode::Backtrack(InputError::new("", ErrorKind::Tag)))); +/// assert_eq!(parser("def|abc"), Err(ErrMode::Backtrack(InputError::new("def|abc", ErrorKind::Tag)))); +/// # } +/// ``` +/// +/// Arbitrary repetitions: +/// ```rust +/// # #[cfg(feature = "std")] { +/// # use winnow::{error::ErrMode, error::{InputError, ErrorKind}, error::Needed}; +/// # use winnow::prelude::*; +/// use winnow::combinator::separated; +/// use winnow::token::tag; +/// +/// fn parser(s: &str) -> IResult<&str, Vec<&str>> { +/// separated(0..=2, "abc", "|").parse_peek(s) +/// } +/// +/// assert_eq!(parser("abc|abc|abc"), Ok(("|abc", vec!["abc", "abc"]))); +/// assert_eq!(parser("abc123abc"), Ok(("123abc", vec!["abc"]))); +/// assert_eq!(parser("abc|def"), Ok(("|def", vec!["abc"]))); +/// assert_eq!(parser(""), Ok(("", vec![]))); +/// assert_eq!(parser("def|abc"), Ok(("def|abc", vec![]))); +/// # } +/// ``` +#[doc(alias = "sep_by")] +#[doc(alias = "sep_by1")] +#[doc(alias = "separated_list0")] +#[doc(alias = "separated_list1")] +#[doc(alias = "separated_m_n")] +#[inline(always)] +pub fn separated( + range: impl Into, + mut parser: P, + mut separator: S, +) -> impl Parser +where + I: Stream, + C: Accumulate, + P: Parser, + S: Parser, + E: ParserError, +{ + let Range { + start_inclusive, + end_inclusive, + } = range.into(); + trace("separated", move |input: &mut I| { + match (start_inclusive, end_inclusive) { + (0, None) => separated0_(&mut parser, &mut separator, input), + (1, None) => separated1_(&mut parser, &mut separator, input), + (start, end) if Some(start) == end => { + separated_n_(start, &mut parser, &mut separator, input) + } + (start, end) => separated_m_n_( + start, + end.unwrap_or(usize::MAX), + &mut parser, + &mut separator, + input, + ), + } + }) +} + /// [`Accumulate`] the output of a parser, interleaved with `sep` /// /// This stops when either parser returns [`ErrMode::Backtrack`]. To instead chain an error up, see @@ -367,6 +499,7 @@ where /// ``` #[doc(alias = "sep_by")] #[doc(alias = "separated_list0")] +#[deprecated(since = "0.5.19", note = "Replaced with `combinator::separated`")] pub fn separated0(mut parser: P, mut sep: S) -> impl Parser where I: Stream, @@ -376,49 +509,67 @@ where E: ParserError, { trace("separated0", move |i: &mut I| { - let mut res = C::initial(None); + separated0_(&mut parser, &mut sep, i) + }) +} - let start = i.checkpoint(); - match parser.parse_next(i) { +fn separated0_( + parser: &mut P, + separator: &mut S, + input: &mut I, +) -> PResult +where + I: Stream, + C: Accumulate, + P: Parser, + S: Parser, + E: ParserError, +{ + let mut acc = C::initial(None); + + let start = input.checkpoint(); + match parser.parse_next(input) { + Err(ErrMode::Backtrack(_)) => { + input.reset(start); + return Ok(acc); + } + Err(e) => return Err(e), + Ok(o) => { + acc.accumulate(o); + } + } + + loop { + let start = input.checkpoint(); + let len = input.eof_offset(); + match separator.parse_next(input) { Err(ErrMode::Backtrack(_)) => { - i.reset(start); - return Ok(res); + input.reset(start); + return Ok(acc); } Err(e) => return Err(e), - Ok(o) => { - res.accumulate(o); - } - } - - loop { - let start = i.checkpoint(); - let len = i.eof_offset(); - match sep.parse_next(i) { - Err(ErrMode::Backtrack(_)) => { - i.reset(start); - return Ok(res); + Ok(_) => { + // infinite loop check + if input.eof_offset() == len { + return Err(ErrMode::assert( + input, + "`separated` separator parser must always consume", + )); } - Err(e) => return Err(e), - Ok(_) => { - // infinite loop check: the parser must always consume - if i.eof_offset() == len { - return Err(ErrMode::assert(i, "sep parsers must always consume")); - } - match parser.parse_next(i) { - Err(ErrMode::Backtrack(_)) => { - i.reset(start); - return Ok(res); - } - Err(e) => return Err(e), - Ok(o) => { - res.accumulate(o); - } + match parser.parse_next(input) { + Err(ErrMode::Backtrack(_)) => { + input.reset(start); + return Ok(acc); + } + Err(e) => return Err(e), + Ok(o) => { + acc.accumulate(o); } } } } - }) + } } /// [`Accumulate`] the output of a parser, interleaved with `sep` @@ -454,6 +605,7 @@ where /// ``` #[doc(alias = "sep_by1")] #[doc(alias = "separated_list1")] +#[deprecated(since = "0.5.19", note = "Replaced with `combinator::separated`")] pub fn separated1(mut parser: P, mut sep: S) -> impl Parser where I: Stream, @@ -463,45 +615,204 @@ where E: ParserError, { trace("separated1", move |i: &mut I| { - let mut res = C::initial(None); + separated1_(&mut parser, &mut sep, i) + }) +} + +fn separated1_( + parser: &mut P, + separator: &mut S, + input: &mut I, +) -> PResult +where + I: Stream, + C: Accumulate, + P: Parser, + S: Parser, + E: ParserError, +{ + let mut acc = C::initial(None); + + // Parse the first element + match parser.parse_next(input) { + Err(e) => return Err(e), + Ok(o) => { + acc.accumulate(o); + } + } - // Parse the first element - match parser.parse_next(i) { + loop { + let start = input.checkpoint(); + let len = input.eof_offset(); + match separator.parse_next(input) { + Err(ErrMode::Backtrack(_)) => { + input.reset(start); + return Ok(acc); + } Err(e) => return Err(e), - Ok(o) => { - res.accumulate(o); + Ok(_) => { + // infinite loop check + if input.eof_offset() == len { + return Err(ErrMode::assert( + input, + "`separated` separator parser must always consume", + )); + } + + match parser.parse_next(input) { + Err(ErrMode::Backtrack(_)) => { + input.reset(start); + return Ok(acc); + } + Err(e) => return Err(e), + Ok(o) => { + acc.accumulate(o); + } + } } } + } +} - loop { - let start = i.checkpoint(); - let len = i.eof_offset(); - match sep.parse_next(i) { - Err(ErrMode::Backtrack(_)) => { - i.reset(start); - return Ok(res); +fn separated_n_( + count: usize, + parser: &mut P, + separator: &mut S, + input: &mut I, +) -> PResult +where + I: Stream, + C: Accumulate, + P: Parser, + S: Parser, + E: ParserError, +{ + let mut acc = C::initial(Some(count)); + + if count == 0 { + return Ok(acc); + } + + match parser.parse_next(input) { + Err(e) => { + return Err(e.append(input, ErrorKind::Many)); + } + Ok(o) => { + acc.accumulate(o); + } + } + + for _ in 1..count { + let len = input.eof_offset(); + match separator.parse_next(input) { + Err(e) => { + return Err(e.append(input, ErrorKind::Many)); + } + Ok(_) => { + // infinite loop check + if input.eof_offset() == len { + return Err(ErrMode::assert( + input, + "`separated` separator parser must always consume", + )); } - Err(e) => return Err(e), - Ok(_) => { - // infinite loop check: the parser must always consume - if i.eof_offset() == len { - return Err(ErrMode::assert(i, "sep parsers must always consume")); + + match parser.parse_next(input) { + Err(e) => { + return Err(e.append(input, ErrorKind::Many)); + } + Ok(o) => { + acc.accumulate(o); } + } + } + } + } - match parser.parse_next(i) { - Err(ErrMode::Backtrack(_)) => { - i.reset(start); - return Ok(res); - } - Err(e) => return Err(e), - Ok(o) => { - res.accumulate(o); + Ok(acc) +} + +fn separated_m_n_( + min: usize, + max: usize, + parser: &mut P, + separator: &mut S, + input: &mut I, +) -> PResult +where + I: Stream, + C: Accumulate, + P: Parser, + S: Parser, + E: ParserError, +{ + if min > max { + return Err(ErrMode::Cut(E::from_error_kind(input, ErrorKind::Many))); + } + + let mut acc = C::initial(Some(min)); + + let start = input.checkpoint(); + match parser.parse_next(input) { + Err(ErrMode::Backtrack(e)) => { + if min == 0 { + input.reset(start); + return Ok(acc); + } else { + return Err(ErrMode::Backtrack(e.append(input, ErrorKind::Many))); + } + } + Err(e) => return Err(e), + Ok(o) => { + acc.accumulate(o); + } + } + + for index in 1..max { + let start = input.checkpoint(); + let len = input.eof_offset(); + match separator.parse_next(input) { + Err(ErrMode::Backtrack(e)) => { + if index < min { + return Err(ErrMode::Backtrack(e.append(input, ErrorKind::Many))); + } else { + input.reset(start); + return Ok(acc); + } + } + Err(e) => { + return Err(e); + } + Ok(_) => { + // infinite loop check + if input.eof_offset() == len { + return Err(ErrMode::assert( + input, + "`separated` separator parser must always consume", + )); + } + + match parser.parse_next(input) { + Err(ErrMode::Backtrack(e)) => { + if index < min { + return Err(ErrMode::Backtrack(e.append(input, ErrorKind::Many))); + } else { + input.reset(start); + return Ok(acc); } } + Err(e) => { + return Err(e); + } + Ok(o) => { + acc.accumulate(o); + } } } } - }) + } + + Ok(acc) } /// Alternates between two parsers, merging the results (left associative) diff --git a/src/combinator/tests.rs b/src/combinator/tests.rs index 9d2b49d8..27fa5345 100644 --- a/src/combinator/tests.rs +++ b/src/combinator/tests.rs @@ -717,13 +717,13 @@ fn permutation_test() { #[cfg(feature = "alloc")] fn separated0_test() { fn multi(i: Partial<&[u8]>) -> IResult, Vec<&[u8]>> { - separated0("abcd", ",").parse_peek(i) + separated(0.., "abcd", ",").parse_peek(i) } fn multi_empty(i: Partial<&[u8]>) -> IResult, Vec<&[u8]>> { - separated0("", ",").parse_peek(i) + separated(0.., "", ",").parse_peek(i) } fn multi_longsep(i: Partial<&[u8]>) -> IResult, Vec<&[u8]>> { - separated0("abcd", "..").parse_peek(i) + separated(0.., "abcd", "..").parse_peek(i) } let a = &b"abcdef"[..]; @@ -773,7 +773,7 @@ fn separated0_test() { #[cfg_attr(debug_assertions, should_panic)] fn separated0_empty_sep_test() { fn empty_sep(i: Partial<&[u8]>) -> IResult, Vec<&[u8]>> { - separated0("abc", "").parse_peek(i) + separated(0.., "abc", "").parse_peek(i) } let i = &b"abcabc"[..]; @@ -792,10 +792,10 @@ fn separated0_empty_sep_test() { #[cfg(feature = "alloc")] fn separated1_test() { fn multi(i: Partial<&[u8]>) -> IResult, Vec<&[u8]>> { - separated1("abcd", ",").parse_peek(i) + separated(1.., "abcd", ",").parse_peek(i) } fn multi_longsep(i: Partial<&[u8]>) -> IResult, Vec<&[u8]>> { - separated1("abcd", "..").parse_peek(i) + separated(1.., "abcd", "..").parse_peek(i) } let a = &b"abcdef"[..]; @@ -838,6 +838,47 @@ fn separated1_test() { ); } +#[test] +#[cfg(feature = "alloc")] +fn separated_test() { + fn multi(i: Partial<&[u8]>) -> IResult, Vec<&[u8]>> { + separated(2..=4, "abcd", ",").parse_peek(i) + } + + let a = &b"abcd,ef"[..]; + let b = &b"abcd,abcd,efgh"[..]; + let c = &b"abcd,abcd,abcd,abcd,efgh"[..]; + let d = &b"abcd,abcd,abcd,abcd,abcd,efgh"[..]; + let e = &b"abcd,ab"[..]; + + assert_eq!( + multi(Partial::new(a)), + Err(ErrMode::Backtrack(error_position!( + &Partial::new(&b"ef"[..]), + ErrorKind::Tag + ))) + ); + let res1 = vec![&b"abcd"[..], &b"abcd"[..]]; + assert_eq!( + multi(Partial::new(b)), + Ok((Partial::new(&b",efgh"[..]), res1)) + ); + let res2 = vec![&b"abcd"[..], &b"abcd"[..], &b"abcd"[..], &b"abcd"[..]]; + assert_eq!( + multi(Partial::new(c)), + Ok((Partial::new(&b",efgh"[..]), res2)) + ); + let res3 = vec![&b"abcd"[..], &b"abcd"[..], &b"abcd"[..], &b"abcd"[..]]; + assert_eq!( + multi(Partial::new(d)), + Ok((Partial::new(&b",abcd,efgh"[..]), res3)) + ); + assert_eq!( + multi(Partial::new(e)), + Err(ErrMode::Incomplete(Needed::new(2))) + ); +} + #[test] #[cfg(feature = "alloc")] fn repeat0_test() { diff --git a/src/stream/tests.rs b/src/stream/tests.rs index 0338310e..0129f6e3 100644 --- a/src/stream/tests.rs +++ b/src/stream/tests.rs @@ -2,7 +2,7 @@ use proptest::prelude::*; use crate::{ - combinator::{separated0, separated_pair}, + combinator::{separated, separated_pair}, PResult, Parser, }; @@ -17,7 +17,7 @@ fn test_fxhashmap_compiles() { Ok(out) } - let _: rustc_hash::FxHashMap = separated0(pair, ',').parse(input).unwrap(); + let _: rustc_hash::FxHashMap = separated(0.., pair, ',').parse(input).unwrap(); } #[test] diff --git a/tests/testsuite/issues.rs b/tests/testsuite/issues.rs index efc4d721..2346b188 100644 --- a/tests/testsuite/issues.rs +++ b/tests/testsuite/issues.rs @@ -125,15 +125,15 @@ fn issue_655() { #[cfg(feature = "alloc")] fn issue_717(i: &[u8]) -> IResult<&[u8], Vec<&[u8]>> { - use winnow::combinator::separated0; + use winnow::combinator::separated; use winnow::token::{tag, take_till1}; - separated0(take_till1([0x0u8]), tag([0x0])).parse_peek(i) + separated(0.., take_till1([0x0u8]), tag([0x0])).parse_peek(i) } mod issue_647 { use super::*; - use winnow::combinator::separated0; + use winnow::combinator::separated; use winnow::token::tag; use winnow::{binary::be_f64, error::ErrMode, IResult}; pub type Stream<'a> = winnow::Partial<&'a [u8]>; @@ -149,7 +149,7 @@ mod issue_647 { input: Stream<'a>, _cs: &f64, ) -> Result<(Stream<'a>, Vec), ErrMode>>> { - separated0(be_f64.complete_err(), tag(",").complete_err()).parse_peek(input) + separated(0.., be_f64.complete_err(), tag(",").complete_err()).parse_peek(input) } fn data(input: Stream<'_>) -> IResult, Data> {