From d4aedfdd72b591e4700e99503250a7aa73a3a184 Mon Sep 17 00:00:00 2001 From: martinohmann Date: Mon, 17 Apr 2023 13:41:51 +0200 Subject: [PATCH] feat: Add `output_stream`/`with_output_stream` to `Parser` This is a draft implementation to solve https://github.com/winnow-rs/winnow/issues/231 --- src/combinator/mod.rs | 92 ++++++++++++++++++++++++++++++- src/parser.rs | 124 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 201 insertions(+), 15 deletions(-) diff --git a/src/combinator/mod.rs b/src/combinator/mod.rs index 4276b06d..09d9d7b4 100644 --- a/src/combinator/mod.rs +++ b/src/combinator/mod.rs @@ -159,7 +159,7 @@ use crate::error::{ContextError, ErrMode, ErrorKind, FromExternalError, Needed, ParseError}; use crate::lib::std::borrow::Borrow; use crate::lib::std::ops::Range; -use crate::stream::{Location, Stream}; +use crate::stream::{Location, Stream, UpdateSlice}; use crate::stream::{Offset, StreamIsPartial}; use crate::trace::trace; use crate::trace::trace_result; @@ -972,6 +972,96 @@ where } } +/// Implementation of [`Parser::output_stream`] +#[cfg_attr(nightly, warn(rustdoc::missing_doc_code_examples))] +pub struct OutputStream +where + F: Parser, + I: UpdateSlice, +{ + parser: F, + i: core::marker::PhantomData, + o: core::marker::PhantomData, + e: core::marker::PhantomData, +} + +impl OutputStream +where + F: Parser, + I: UpdateSlice, +{ + pub(crate) fn new(parser: F) -> Self { + Self { + parser, + i: Default::default(), + o: Default::default(), + e: Default::default(), + } + } +} + +impl Parser for OutputStream +where + F: Parser, + I: UpdateSlice, +{ + fn parse_next(&mut self, input: I) -> IResult { + match self.parser.parse_next(input.clone()) { + Ok((remaining, _)) => { + let offset = input.offset_to(&remaining); + let (remaining, slice) = input.next_slice(offset); + Ok((remaining, input.update_slice(slice))) + } + Err(e) => Err(e), + } + } +} + +/// Implementation of [`Parser::with_output_stream`] +#[cfg_attr(nightly, warn(rustdoc::missing_doc_code_examples))] +pub struct WithOutputStream +where + F: Parser, + I: UpdateSlice, +{ + parser: F, + i: core::marker::PhantomData, + o: core::marker::PhantomData, + e: core::marker::PhantomData, +} + +impl WithOutputStream +where + F: Parser, + I: UpdateSlice, +{ + pub(crate) fn new(parser: F) -> Self { + Self { + parser, + i: Default::default(), + o: Default::default(), + e: Default::default(), + } + } +} + +impl Parser for WithOutputStream +where + F: Parser, + I: UpdateSlice, +{ + fn parse_next(&mut self, input: I) -> IResult { + match self.parser.parse_next(input.clone()) { + Ok((remaining, output)) => { + let offset = input.offset_to(&remaining); + let (remaining, slice) = input.next_slice(offset); + Ok((remaining, (output, input.update_slice(slice)))) + } + Err(e) => Err(e), + } + } +} + /// Implementation of [`Parser::span`] #[cfg_attr(nightly, warn(rustdoc::missing_doc_code_examples))] pub struct Span diff --git a/src/parser.rs b/src/parser.rs index 26415909..aee5c83e 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2,7 +2,9 @@ use crate::combinator::*; use crate::error::{ContextError, FromExternalError, IResult, ParseError}; -use crate::stream::{AsChar, Compare, Location, Offset, ParseSlice, Stream, StreamIsPartial}; +use crate::stream::{ + AsChar, Compare, Location, Offset, ParseSlice, Stream, StreamIsPartial, UpdateSlice, +}; /// Core trait for parsing /// @@ -187,19 +189,21 @@ pub trait Parser { /// Produce the consumed input as produced value. /// + /// The produced value is of type `Stream::Slice`. If you're looking for an alternative that + /// returns the original input `Stream`'s type, use [`output_stream`](Parser::output_stream) + /// instead. + /// /// # Example /// /// ```rust - /// # use winnow::{error::ErrMode,error::ErrorKind, error::Error, IResult, Parser}; - /// use winnow::character::{alpha1}; + /// # use winnow::{error::{ErrMode, Error, ErrorKind}, IResult, Parser}; + /// use winnow::character::alpha1; /// use winnow::sequence::separated_pair; - /// # fn main() { /// /// let mut parser = separated_pair(alpha1, ',', alpha1).recognize(); /// /// assert_eq!(parser.parse_next("abcd,efgh"), Ok(("", "abcd,efgh"))); /// assert_eq!(parser.parse_next("abcd;"),Err(ErrMode::Backtrack(Error::new(";", ErrorKind::Verify)))); - /// # } /// ``` #[doc(alias = "concat")] fn recognize(self) -> Recognize @@ -212,29 +216,29 @@ pub trait Parser { /// Produce the consumed input with the output /// - /// Functions similarly to [recognize][Parser::recognize] except it - /// returns the parser output as well. + /// Functions similarly to [`recognize`][Parser::recognize] except it returns the parser output + /// as well. /// /// This can be useful especially in cases where the output is not the same type /// as the input, or the input is a user defined type. /// + /// The consumed input's value is of type `Stream::Slice`. If you're looking for an alternative + /// that returns the original input `Stream`'s type, use + /// [`with_output_stream`](Parser::with_output_stream) instead. + /// /// Returned tuple is of the format `(produced output, consumed input)`. /// /// # Example /// /// ```rust - /// # use winnow::prelude::*; - /// # use winnow::{error::ErrMode,error::ErrorKind, error::Error, IResult}; - /// use winnow::character::{alpha1}; - /// use winnow::bytes::tag; + /// # use winnow::{error::{ErrMode, Error, ErrorKind}, IResult, Parser}; + /// use winnow::character::alpha1; /// use winnow::sequence::separated_pair; /// /// fn inner_parser(input: &str) -> IResult<&str, bool> { /// "1234".value(true).parse_next(input) /// } /// - /// # fn main() { - /// /// let mut consumed_parser = separated_pair(alpha1, ',', alpha1).value(true).with_recognized(); /// /// assert_eq!(consumed_parser.parse_next("abcd,efgh1"), Ok(("1", (true, "abcd,efgh")))); @@ -247,7 +251,6 @@ pub trait Parser { /// /// assert_eq!(recognize_parser.parse_next("1234"), consumed_parser.parse_next("1234")); /// assert_eq!(recognize_parser.parse_next("abcd"), consumed_parser.parse_next("abcd")); - /// # } /// ``` #[doc(alias = "consumed")] fn with_recognized(self) -> WithRecognized @@ -258,6 +261,99 @@ pub trait Parser { WithRecognized::new(self) } + /// Produce the consumed input as produced value. + /// + /// The produced value is of the same type as the input `Stream`. If you're looking for an + /// alternative that returns `Stream::Slice`, use [`recognize`](Parser::recognize) instead. + /// + /// # Example + /// + /// ```rust + /// # use winnow::{error::{ErrMode, Error, ErrorKind}, IResult, Parser}; + /// use winnow::character::alpha1; + /// use winnow::sequence::separated_pair; + /// use winnow::stream::BStr; + /// + /// let mut parser = separated_pair(alpha1, ',', alpha1).output_stream(); + /// + /// assert_eq!( + /// parser.parse_next(BStr::new("abcd,efgh")), + /// Ok((BStr::new(""), BStr::new("abcd,efgh"))), + /// ); + /// assert_eq!( + /// parser.parse_next(BStr::new("abcd;")), + /// Err(ErrMode::Backtrack(Error::new(BStr::new(";"), ErrorKind::Verify))), + /// ); + /// ``` + fn output_stream(self) -> OutputStream + where + Self: core::marker::Sized, + I: UpdateSlice, + { + OutputStream::new(self) + } + + /// Produce the consumed input with the output. + /// + /// Functions similarly to [`output_stream`][Parser::output_stream] except it returns the + /// parser output as well. + /// + /// This can be useful especially in cases where the output is not the same type as the input. + /// + /// The consumed input's value is of the same type as the input `Stream`. If you're looking for + /// an alternative that returns `Stream::Slice`, use + /// [`with_recognized`](Parser::with_recognized) instead. + /// + /// Returned tuple is of the format `(produced output, consumed input)`. + /// + /// # Example + /// + /// ```rust + /// # use winnow::{error::{ErrMode, Error, ErrorKind}, IResult, Parser}; + /// use winnow::character::alpha1; + /// use winnow::sequence::separated_pair; + /// use winnow::stream::BStr; + /// + /// fn inner_parser(input: &BStr) -> IResult<&BStr, bool> { + /// "1234".value(true).parse_next(input) + /// } + /// + /// let mut consumed_parser = separated_pair(alpha1, ',', alpha1) + /// .value(true) + /// .with_output_stream(); + /// + /// assert_eq!( + /// consumed_parser.parse_next(BStr::new("abcd,efgh1")), + /// Ok((BStr::new("1"), (true, BStr::new("abcd,efgh")))), + /// ); + /// assert_eq!( + /// consumed_parser.parse_next(BStr::new("abcd;")), + /// Err(ErrMode::Backtrack(Error::new(BStr::new(";"), ErrorKind::Verify))), + /// ); + /// + /// // The second output (representing the consumed input) should be the same as that of the + /// // `output_stream` parser. + /// let mut output_stream_parser = inner_parser.output_stream(); + /// let mut consumed_parser = inner_parser.with_output_stream() + /// .map(|(output, output_stream)| output_stream); + /// + /// assert_eq!( + /// output_stream_parser.parse_next(BStr::new("1234")), + /// consumed_parser.parse_next(BStr::new("1234")), + /// ); + /// assert_eq!( + /// output_stream_parser.parse_next(BStr::new("abcd")), + /// consumed_parser.parse_next(BStr::new("abcd")), + /// ); + /// ``` + fn with_output_stream(self) -> WithOutputStream + where + Self: core::marker::Sized, + I: UpdateSlice, + { + WithOutputStream::new(self) + } + /// Produce the location of the consumed input as produced value. /// /// # Example