diff --git a/src/_topic/nom.rs b/src/_topic/nom.rs index 23c9ac0e..66b2806a 100644 --- a/src/_topic/nom.rs +++ b/src/_topic/nom.rs @@ -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. @@ -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` 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`) 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`). +//! 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`] and parsers will adjust +//! +//! Instead, you tag the input type (`I`) by wrapping it in [`Partial`] and parsers will adjust //! their behavior accordingly. //! See [partial] special topic. //! @@ -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` which could be a `Vec`, a `usize` for `count` //! variants, or `()` to do no extra work. //! @@ -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. //! @@ -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`. @@ -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