Skip to content

Commit 957f667

Browse files
committed
feat: Add recognize_till
This is a variant of `repeat_till` that recognizes the repeated portion of the input instead of accumulating it. See: [“Recognizing `repeat_till0` to `()` without the suffix”][1] [1]: #336 (reply in thread)
1 parent e826529 commit 957f667

File tree

1 file changed

+95
-0
lines changed

1 file changed

+95
-0
lines changed

src/combinator/multi.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::error::ErrMode;
55
use crate::error::ErrorKind;
66
use crate::error::ParserError;
77
use crate::stream::Accumulate;
8+
use crate::stream::Offset;
89
use crate::stream::Range;
910
use crate::stream::Stream;
1011
use crate::PResult;
@@ -1307,3 +1308,97 @@ where
13071308

13081309
Ok(acc)
13091310
}
1311+
1312+
/// Call the `repeat` parser until the `end` parser produces a result.
1313+
///
1314+
/// Then, return the input consumed until the `end` parser was called, and the result of the `end`
1315+
/// parser.
1316+
///
1317+
/// # Example
1318+
///
1319+
/// ```rust
1320+
/// # #[cfg(feature = "std")] {
1321+
/// # use winnow::{error::ErrMode, error::{InputError, ErrorKind}, error::Needed};
1322+
/// # use winnow::prelude::*;
1323+
/// use winnow::ascii::digit1;
1324+
/// use winnow::combinator::recognize_till;
1325+
///
1326+
/// fn parser(s: &str) -> IResult<&str, (&str, char)> {
1327+
/// recognize_till(
1328+
/// digit1,
1329+
/// ' ',
1330+
/// ).parse_peek(s)
1331+
/// }
1332+
///
1333+
/// assert_eq!(parser("123 "), Ok(("", ("123", ' '))));
1334+
/// assert_eq!(parser("0 a"), Ok(("a", ("0", ' '))));
1335+
/// assert_eq!(parser("123a"), Err(ErrMode::Backtrack(InputError::new("a", ErrorKind::Slice))));
1336+
/// assert_eq!(parser(""), Err(ErrMode::Backtrack(InputError::new("", ErrorKind::Slice))));
1337+
/// # }
1338+
/// ```
1339+
///
1340+
/// ```rust
1341+
/// # #[cfg(feature = "std")] {
1342+
/// # use winnow::{error::ErrMode, error::{InputError, ErrorKind}, error::Needed};
1343+
/// # use winnow::prelude::*;
1344+
/// use winnow::combinator::alt;
1345+
/// use winnow::combinator::preceded;
1346+
/// use winnow::combinator::recognize_till;
1347+
/// use winnow::token::take_till;
1348+
///
1349+
/// fn parser(s: &str) -> IResult<&str, &str> {
1350+
/// preceded(
1351+
/// '\'',
1352+
/// recognize_till(
1353+
/// alt((
1354+
/// preceded('\'', take_till(0.., '\'')),
1355+
/// take_till(1.., '\''),
1356+
/// )),
1357+
/// "' ",
1358+
/// ).map(|(name, _end_quote)| name)
1359+
/// ).parse_peek(s)
1360+
/// }
1361+
///
1362+
/// assert_eq!(parser("'Puppy' "), Ok(("", "Puppy")));
1363+
/// assert_eq!(parser("'Dog'' "), Ok(("", "Dog'")));
1364+
/// assert_eq!(parser("'Isn't' that nice?"), Ok(("that nice?", "Isn't")));
1365+
/// assert_eq!(parser("'' after"), Ok(("after", "")));
1366+
///
1367+
/// assert_eq!(parser("'Puppy'dog"), Err(ErrMode::Backtrack(InputError::new("", ErrorKind::Slice))));
1368+
/// # }
1369+
/// ```
1370+
pub fn recognize_till<I, Discard, O, E>(
1371+
mut repeat: impl Parser<I, Discard, E>,
1372+
mut end: impl Parser<I, O, E>,
1373+
) -> impl Parser<I, (<I as Stream>::Slice, O), E>
1374+
where
1375+
I: Stream,
1376+
E: ParserError<I>,
1377+
{
1378+
move |input: &mut I| {
1379+
let start = input.checkpoint();
1380+
1381+
loop {
1382+
let before_end = input.checkpoint();
1383+
match end.parse_next(input) {
1384+
Ok(end_parsed) => {
1385+
let after_end = input.checkpoint();
1386+
1387+
input.reset(&start);
1388+
let input_until_end = input.next_slice(before_end.offset_from(&start));
1389+
input.reset(&after_end);
1390+
1391+
return Ok((input_until_end, end_parsed));
1392+
}
1393+
Err(ErrMode::Backtrack(_)) => {
1394+
input.reset(&before_end);
1395+
match repeat.parse_next(input) {
1396+
Ok(_) => {}
1397+
Err(e) => return Err(e.append(input, &before_end, ErrorKind::Many)),
1398+
}
1399+
}
1400+
Err(e) => return Err(e),
1401+
}
1402+
}
1403+
}
1404+
}

0 commit comments

Comments
 (0)