Skip to content

Commit

Permalink
Merge pull request #719 from epage/cherry
Browse files Browse the repository at this point in the history
Smooth out v0.7 migration
  • Loading branch information
epage authored Jan 27, 2025
2 parents 23d51df + 4f9239c commit e19d2b2
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 40 deletions.
21 changes: 5 additions & 16 deletions examples/json/parser_dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use std::str;
use winnow::prelude::*;
use winnow::{
ascii::float,
combinator::cut_err,
combinator::empty,
combinator::fail,
combinator::peek,
Expand Down Expand Up @@ -91,17 +90,13 @@ fn string<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, StrContext>>(
) -> PResult<String, E> {
preceded(
'\"',
// `cut_err` transforms an `ErrMode::Backtrack(e)` to `ErrMode::Cut(e)`, signaling to
// combinators like `alt` that they should not try other parsers. We were in the
// right branch (since we found the `"` character) but encountered an error when
// parsing the string
cut_err(terminated(
terminated(
repeat(0.., character).fold(String::new, |mut string, c| {
string.push(c);
string
}),
'\"',
)),
),
)
// `context` lets you add a static string to errors to provide more information in the
// error chain (to indicate which parser had an error)
Expand Down Expand Up @@ -169,10 +164,7 @@ fn array<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, StrContext>>(
) -> PResult<Vec<JsonValue>, E> {
preceded(
('[', ws),
cut_err(terminated(
separated(0.., json_value, (ws, ',', ws)),
(ws, ']'),
)),
terminated(separated(0.., json_value, (ws, ',', ws)), (ws, ']')),
)
.context(StrContext::Expected("array".into()))
.parse_next(input)
Expand All @@ -183,10 +175,7 @@ fn object<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, StrContext>>(
) -> PResult<HashMap<String, JsonValue>, E> {
preceded(
('{', ws),
cut_err(terminated(
separated(0.., key_value, (ws, ',', ws)),
(ws, '}'),
)),
terminated(separated(0.., key_value, (ws, ',', ws)), (ws, '}')),
)
.context(StrContext::Expected("object".into()))
.parse_next(input)
Expand All @@ -195,7 +184,7 @@ fn object<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, StrContext>>(
fn key_value<'i, E: ParserError<Stream<'i>> + AddContext<Stream<'i>, StrContext>>(
input: &mut Stream<'i>,
) -> PResult<(String, JsonValue), E> {
separated_pair(string, cut_err((ws, ':', ws)), json_value).parse_next(input)
separated_pair(string, (ws, ':', ws), json_value).parse_next(input)
}

/// Parser combinators are constructed from the bottom up:
Expand Down
81 changes: 79 additions & 2 deletions src/_topic/nom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
//! - [gitoxide](https://github.com/Byron/gitoxide/pull/956) (gradual migration from nom
//! to winnow 0.5)
//!
//! ## API differences
//! ## Differences
//!
//! ### Renamed APIs
//!
Expand All @@ -64,9 +64,75 @@
//! - Search the docs for the `nom` parser
//! - See the [List of combinators][crate::combinator]
//!
//! ### GATs
//!
//! `nom` v8 back-propagates how you will use a parser to parser functions using a language feature
//! called GATs.
//! Winnow avoids this.
//!
//! Benefits for avoiding GATs:
//! - Predictable performance as writing; idiomatic `fn(&mut I) -> Result<O>` parser sever the
//! back-propagation from GATs.
//! - No "eek out X% perf improvement" pressure to contort a parser to be written declaratively
//! that is better written imperatively
//! - Built-in parsers serve are simple examples of idiomatic parsers
//! - Faster build times and smaller binary size as parsers only need to be generated for one mode, not upto 6
//!
//! Downsides
//! - Performance
//!
//! #### Partial/streaming parsers
//!
//! `nom` v8 back-propagates whether `Parser::parse_complete` was used to select `complete`
//! parsers.
//! Previously, users had ensure consistently using a parser from the `streaming` or `complete` module.
//! Instead, we tag the input type (`I`) by wrapping it in [`Partial<I>`] and parsers will adjust
//! their behavior accordingly.
//! See [partial] special topic.
//!
//! #### Eliding Output
//!
//! `nom` v8 back-propagates whether an Output will be used and skips its creation.
//! For example, `value(Null, many0(_))` will avoid creating and pushing to a `Vec`.
//! Previously, users had to select `count_many0` over `many0` to avoid creating a `Vec`.
//! Instead, `repeat` returns an `impl Accumulate<T>` which could be a `Vec`, a `usize` for `count`
//! variants, or `()` to do no extra work.
//!
//! #### Eliding Backtracked Errors
//!
//! Under the hood, [`alt`] is an `if-not-error-else` ladder, see [`_tutorial::chapter_3`].
//! nom v8 back-propagates whether the error will be discarded and avoids any expensive work done
//! for rich error messages.
//! Instead, [`ContextError`] and other changes have made it so errors have very little overhead.
//! [`dispatch!`] can also be used in some situations to avoid `alt`s overhead.
//!
//! ### Parsers return [`Stream::Slice`], rather than [`Stream`]
//!
//! In `nom`, parsers like [`take_while`] parse a [`Stream`] and return a [`Stream`].
//! When wrapping the input, like with [`Stateful`],
//! you have to unwrap the input to integrate it in your application,
//! requires [`Stream`] to be `Clone` (which requires `RefCell` for mutable external state),
//! and is then expensive to `clone()`.
//! Instead, [`Stream::Slice`] was added to track the intended type for parsers to return.
//!
//! ### `&mut I`
//!
//! For an explanation of this change, see [Why `winnow`][super::why]
//! `winnow` switched from pure-function parser (`Fn(I) -> (I, O)` to `Fn(&mut I) -> O`).
//! On error, `i` is left pointing at where the error happened.
//!
//! Benefits:
//! - Cleaner code: Removes need to pass `i` everywhere and makes changes to `i` more explicit
//! - Correctness: No forgetting to chain `i` through a parser
//! - Flexibility: `I` does not need to be `Copy` or even `Clone`. For example, [`Stateful`] can use `&mut S` instead of `RefCell<S>`.
//! - Performance: `Result::Ok` is smaller without `i`, reducing the risk that the output will be
//! returned on the stack, rather than the much faster CPU registers.
//! `Result::Err` can also be smaller because the error type does not need to carry `i` to point
//! to the error.
//! See also [#72](https://github.com/winnow-rs/winnow/issues/72).
//!
//! Downsides:
//! - When returning a slice, you have to add a lifetime (`fn foo<'i>(i: &mut &i str) -> ModalResult<&i str>`)
//! - When writing a closure, you need to annotate the type (`|i: &mut _|`, at least the full type isn't needed)
//!
//! To save and restore from intermediate states, [`Stream::checkpoint`] and [`Stream::reset`] can help:
//! ```rust
Expand Down Expand Up @@ -101,4 +167,15 @@
//! ```
#![allow(unused_imports)]
use crate::_topic::partial;
use crate::_tutorial;
use crate::combinator::alt;
use crate::combinator::dispatch;
use crate::error::ContextError;
use crate::error::ErrMode;
use crate::error::ModalResult;
use crate::stream::Accumulate;
use crate::stream::Partial;
use crate::stream::Stateful;
use crate::stream::Stream;
use crate::token::take_while;
23 changes: 3 additions & 20 deletions src/_topic/why.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,26 +72,9 @@
//! and to not block users on new features being merged while `winnow` aims to include all the
//! fundamentals for parsing to ensure the experience is cohesive and high quality.
//!
//! See also our [nom migration guide][super::nom]
//!
//! ### Design trade-offs
//!
//! `winnow` switched from pure-function parser (`Fn(I) -> (I, O)` to `Fn(&mut I) -> O`).
//! On error, `i` is left pointing at where the error happened.
//!
//! Benefits:
//! - Cleaner code: Removes need to pass `i` everywhere and makes changes to `i` more explicit
//! - Correctness: No forgetting to chain `i` through a parser
//! - Flexibility: `I` does not need to be `Copy` or even `Clone`. For example, [`Stateful`] can use `&mut S` instead of `RefCell<S>`.
//! - Performance: `Result::Ok` is smaller without `i`, reducing the risk that the output will be
//! returned on the stack, rather than the much faster CPU registers.
//! `Result::Err` can also be smaller because the error type does not need to carry `i` to point
//! to the error.
//! See also [#72](https://github.com/winnow-rs/winnow/issues/72).
//!
//! Downsides:
//! - When returning a slice, you have to add a lifetime (`fn foo<'i>(i: &mut &i str) -> PResult<&i str>`)
//! - When writing a closure, you need to annotate the type (`|i: &mut _|`, at least the full type isn't needed)
//! For more details, see the [design differences][super::nom#api-differences].
//!
//! See also our [nom migration guide][super::nom#migrating-from-nom].
//!
//! ## `chumsky`
//!
Expand Down
8 changes: 6 additions & 2 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use crate::stream::Stream;
#[allow(unused_imports)] // Here for intra-doc links
use crate::Parser;

/// For use with [`Parser::parse_next`]
/// [Modal error reporting][ErrMode] for [`Parser::parse_next`]
///
/// - `Ok(O)` is the parsed value
/// - [`Err(ErrMode<E>)`][ErrMode] is the error along with how to respond to it
Expand All @@ -40,7 +40,11 @@ use crate::Parser;
/// When integrating into the result of the application, see
/// - [`Parser::parse`]
/// - [`ErrMode::into_inner`]
pub type PResult<O, E = ContextError> = Result<O, ErrMode<E>>;
pub type ModalResult<O, E = ContextError> = Result<O, ErrMode<E>>;

/// Deprecated, replaced with [`ModalResult`]
#[deprecated(since = "0.6.23", note = "Replaced with ModalResult")]
pub type PResult<O, E = ContextError> = ModalResult<O, E>;

/// Deprecated, replaced with [`PResult`]
#[deprecated(since = "0.6.25", note = "Replaced with `PResult`")]
Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ pub mod _tutorial;
pub mod prelude {
pub use crate::stream::StreamIsPartial as _;
pub use crate::IResult;
pub use crate::ModalParser;
pub use crate::ModalResult;
pub use crate::PResult;
pub use crate::Parser;
#[cfg(feature = "unstable-recover")]
Expand All @@ -154,6 +156,7 @@ pub mod prelude {
}

pub use error::IResult;
pub use error::ModalResult;
pub use error::PResult;
pub use parser::*;
pub use stream::BStr;
Expand Down
5 changes: 5 additions & 0 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1160,6 +1160,11 @@ macro_rules! impl_parser_for_tuples {
}
}

/// Trait alias for [`Parser`] to ease the transition to the next release
pub trait ModalParser<I, O, E>: Parser<I, O, E> {}

impl<I, O, E, P> ModalParser<I, O, E> for P where P: Parser<I, O, E> {}

/// Collect all errors when parsing the input
///
/// [`Parser`]s will need to use [`Recoverable<I, _>`] for their input.
Expand Down

0 comments on commit e19d2b2

Please sign in to comment.