From 8cd859b7905d77a6ebf0cf00257ac0d06bf5e574 Mon Sep 17 00:00:00 2001 From: Rebecca Turner Date: Fri, 14 Jun 2024 17:01:12 -0700 Subject: [PATCH] Add `transform_till` and `recognize_till` parsers Upstream: https://github.com/winnow-rs/winnow/pull/541 --- src/ghci/parse/mod.rs | 3 ++ src/ghci/parse/transform_till.rs | 81 ++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 src/ghci/parse/transform_till.rs diff --git a/src/ghci/parse/mod.rs b/src/ghci/parse/mod.rs index ae4a31a..b067cd6 100644 --- a/src/ghci/parse/mod.rs +++ b/src/ghci/parse/mod.rs @@ -9,6 +9,7 @@ mod module_set; mod show_paths; mod show_targets; mod target_kind; +mod transform_till; use haskell_grammar::module_name; use lines::rest_of_line; @@ -28,3 +29,5 @@ pub use show_paths::parse_show_paths; pub use show_paths::ShowPaths; pub use show_targets::parse_show_targets; pub use target_kind::TargetKind; +pub use transform_till::recognize_till; +pub use transform_till::transform_till; diff --git a/src/ghci/parse/transform_till.rs b/src/ghci/parse/transform_till.rs new file mode 100644 index 0000000..f7e6e32 --- /dev/null +++ b/src/ghci/parse/transform_till.rs @@ -0,0 +1,81 @@ +use winnow::combinator::eof; +use winnow::combinator::terminated; +use winnow::error::ErrMode; +use winnow::error::ErrorKind; +use winnow::error::ParserError; +use winnow::stream::Offset; +use winnow::stream::Stream; +use winnow::stream::StreamIsPartial; +use winnow::Parser; + +/// Call the `repeat` parser until the `end` parser produces a result. +/// +/// Then, return the input consumed until the `end` parser was called, and the result of the `end` +/// parser. +/// +/// See: +pub fn recognize_till( + mut repeat: impl Parser, + mut end: impl Parser, +) -> impl Parser::Slice, O), E> +where + I: Stream, + E: ParserError, +{ + move |input: &mut I| { + let start = input.checkpoint(); + + loop { + let before_end = input.checkpoint(); + match end.parse_next(input) { + Ok(end_parsed) => { + let after_end = input.checkpoint(); + + let offset_to_before_end = before_end.offset_from(&start); + input.reset(start); + let input_until_end = input.next_slice(offset_to_before_end); + input.reset(after_end); + + return Ok((input_until_end, end_parsed)); + } + Err(ErrMode::Backtrack(_)) => { + input.reset(before_end); + match repeat.parse_next(input) { + Ok(_) => {} + Err(e) => return Err(e.append(input, ErrorKind::Many)), + } + } + Err(e) => return Err(e), + } + } + } +} + +/// Like [`recognize_till`], but it also applies a `transform` parser to the recognized input. +pub fn transform_till( + mut repeat: impl Parser, + mut transform: impl Parser<::Slice, O1, E>, + mut end: impl Parser, +) -> impl Parser +where + I: Stream, + E: ParserError, + E: ParserError<::Slice>, + ::Slice: Stream + StreamIsPartial, +{ + move |input: &mut I| { + let (mut until_end, end_parsed) = + recognize_till(repeat.by_ref(), end.by_ref()).parse_next(input)?; + + let inner_parsed = terminated(transform.by_ref(), eof) + .parse_next(&mut until_end) + .map_err(|err_mode| match err_mode { + ErrMode::Incomplete(_) => { + panic!("complete parsers should not report `ErrMode::Incomplete(_)`") + } + ErrMode::Backtrack(inner) | ErrMode::Cut(inner) => ErrMode::Cut(inner), + })?; + + Ok((inner_parsed, end_parsed)) + } +}