Skip to content

Commit

Permalink
Merge pull request #732 from epage/cherry
Browse files Browse the repository at this point in the history
docs(topic): Clarify and cleanup nom topic
  • Loading branch information
epage authored Jan 30, 2025
2 parents 6b80d18 + 74e79f7 commit be1a485
Showing 1 changed file with 68 additions and 19 deletions.
87 changes: 68 additions & 19 deletions src/_topic/nom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@
//!
//! ## Differences
//!
//! These are key differences to help Nom users adapt to writing parsers with Winnow.
//!
//! ### Renamed APIs
//!
//! Names have changed for consistency or clarity.
Expand All @@ -68,25 +70,36 @@
//!
//! `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
//! Winnow has made the conscious choice not to use this feature, finding alternative ways of
//! getting most of the benefits.
//!
//! Benefits of GATs:
//! - Performance as the compiler is able to instantiate copies of a parser that are
//! better tailored to how it will be used, like discarding unused allocations for output or
//! errors.
//!
//! Benefits of not using GATs:
//! - Predictable performance:
//! With GATs, seemingly innocuous changes like choosing to hand write a parser using idiomatic function parsers
//! (`fn(&mut I) -> Result<O>`) can cause surprising slow downs because these functions sever the back-propagation from GATs.
//! The causes of these slowdowns could be hard to identify by inspection or profiling.
//! - No "eek out X% perf improvement" pressure to contort a parser
//! that is more easily written imperatively
//! to be written declaratively
//! so it can preserve the back-propagation from GATs.
//! - Built-in parsers serve are can serve as examples to users of idiomatic function parsers
//! (`fn(&mut I) -> Result<O>`).
//! With GATs, built-in parsers tend to be complex implementations of traits.
//! - Faster build times and smaller binary size as parsers only need to be generated for one mode,
//! not upto 8
//!
//! #### 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
//!
//! Instead, you tag the input type (`I`) by wrapping it in [`Partial<I>`] and parsers will adjust
//! their behavior accordingly.
//! See [partial] special topic.
//!
Expand All @@ -95,6 +108,7 @@
//! `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.
//!
Expand All @@ -103,6 +117,7 @@
//! 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.
//!
Expand All @@ -111,16 +126,19 @@
//! 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()`.
//! and it requires [`Stream`] to be `Clone`
//! (which requires `RefCell` for mutable external state and can be expensive).
//!
//! Instead, [`Stream::Slice`] was added to track the intended type for parsers to return.
//! If you want to then parse the slice, you then need to take it and turn it back into a
//! [`Stream`].
//!
//! ### `&mut I`
//!
//! `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:
//! Benefits of `Fn(&mut I) -> O`:
//! - 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>`.
Expand All @@ -130,9 +148,40 @@
//! 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)
//! Benefits of `Fn(I) -> (I, O)`:
//! - Pure functions can be easier to reason about
//! - Less boilerplate in some situations (see below)
//! - Less syntactic noise in some situations (see below)
//!
//! When returning a slice from the input, you have to add a lifetime:
//! ```rust
//! # use winnow::prelude::*;
//! fn foo<'i>(i: &mut &'i str) -> ModalResult<&'i str> {
//! # Ok("")
//! // ...
//! }
//! ```
//!
//! When writing a closure, you need to annotate the type
//! ```rust
//! # use winnow::prelude::*;
//! # use winnow::combinator::alt;
//! # use winnow::error::ContextError;
//! # let mut input = "";
//! # fn foo<'i>() -> impl ModalParser<&'i str, &'i str, ContextError> {
//! alt((
//! |i: &mut _| {
//! # Ok("")
//! // ...
//! },
//! |i: &mut _| {
//! # Ok("")
//! // ...
//! },
//! ))
//! # }
//! ```
//! *(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

0 comments on commit be1a485

Please sign in to comment.