Skip to content

Commit b5d4276

Browse files
authored
Merge pull request #731 from epage/nom
docs(topic): Clarify and cleanup nom topic
2 parents 48ba49b + caa3ba9 commit b5d4276

File tree

1 file changed

+68
-19
lines changed

1 file changed

+68
-19
lines changed

src/_topic/nom.rs

+68-19
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@
5656
//!
5757
//! ## Differences
5858
//!
59+
//! These are key differences to help Nom users adapt to writing parsers with Winnow.
60+
//!
5961
//! ### Renamed APIs
6062
//!
6163
//! Names have changed for consistency or clarity.
@@ -68,25 +70,36 @@
6870
//!
6971
//! `nom` v8 back-propagates how you will use a parser to parser functions using a language feature
7072
//! called GATs.
71-
//! Winnow avoids this.
72-
//!
73-
//! Benefits for avoiding GATs:
74-
//! - Predictable performance as writing; idiomatic `fn(&mut I) -> Result<O>` parser sever the
75-
//! back-propagation from GATs.
76-
//! - No "eek out X% perf improvement" pressure to contort a parser to be written declaratively
77-
//! that is better written imperatively
78-
//! - Built-in parsers serve are simple examples of idiomatic parsers
79-
//! - Faster build times and smaller binary size as parsers only need to be generated for one mode, not upto 6
80-
//!
81-
//! Downsides
82-
//! - Performance
73+
//! Winnow has made the conscious choice not to use this feature, finding alternative ways of
74+
//! getting most of the benefits.
75+
//!
76+
//! Benefits of GATs:
77+
//! - Performance as the compiler is able to instantiate copies of a parser that are
78+
//! better tailored to how it will be used, like discarding unused allocations for output or
79+
//! errors.
80+
//!
81+
//! Benefits of not using GATs:
82+
//! - Predictable performance:
83+
//! With GATs, seemingly innocuous changes like choosing to hand write a parser using idiomatic function parsers
84+
//! (`fn(&mut I) -> Result<O>`) can cause surprising slow downs because these functions sever the back-propagation from GATs.
85+
//! The causes of these slowdowns could be hard to identify by inspection or profiling.
86+
//! - No "eek out X% perf improvement" pressure to contort a parser
87+
//! that is more easily written imperatively
88+
//! to be written declaratively
89+
//! so it can preserve the back-propagation from GATs.
90+
//! - Built-in parsers serve are can serve as examples to users of idiomatic function parsers
91+
//! (`fn(&mut I) -> Result<O>`).
92+
//! With GATs, built-in parsers tend to be complex implementations of traits.
93+
//! - Faster build times and smaller binary size as parsers only need to be generated for one mode,
94+
//! not upto 8
8395
//!
8496
//! #### Partial/streaming parsers
8597
//!
8698
//! `nom` v8 back-propagates whether `Parser::parse_complete` was used to select `complete`
8799
//! parsers.
88100
//! Previously, users had ensure consistently using a parser from the `streaming` or `complete` module.
89-
//! Instead, we tag the input type (`I`) by wrapping it in [`Partial<I>`] and parsers will adjust
101+
//!
102+
//! Instead, you tag the input type (`I`) by wrapping it in [`Partial<I>`] and parsers will adjust
90103
//! their behavior accordingly.
91104
//! See [partial] special topic.
92105
//!
@@ -95,6 +108,7 @@
95108
//! `nom` v8 back-propagates whether an Output will be used and skips its creation.
96109
//! For example, `value(Null, many0(_))` will avoid creating and pushing to a `Vec`.
97110
//! Previously, users had to select `count_many0` over `many0` to avoid creating a `Vec`.
111+
//!
98112
//! Instead, `repeat` returns an `impl Accumulate<T>` which could be a `Vec`, a `usize` for `count`
99113
//! variants, or `()` to do no extra work.
100114
//!
@@ -103,6 +117,7 @@
103117
//! Under the hood, [`alt`] is an `if-not-error-else` ladder, see [`_tutorial::chapter_3`].
104118
//! nom v8 back-propagates whether the error will be discarded and avoids any expensive work done
105119
//! for rich error messages.
120+
//!
106121
//! Instead, [`ContextError`] and other changes have made it so errors have very little overhead.
107122
//! [`dispatch!`] can also be used in some situations to avoid `alt`s overhead.
108123
//!
@@ -111,16 +126,19 @@
111126
//! In `nom`, parsers like [`take_while`] parse a [`Stream`] and return a [`Stream`].
112127
//! When wrapping the input, like with [`Stateful`],
113128
//! you have to unwrap the input to integrate it in your application,
114-
//! requires [`Stream`] to be `Clone` (which requires `RefCell` for mutable external state),
115-
//! and is then expensive to `clone()`.
129+
//! and it requires [`Stream`] to be `Clone`
130+
//! (which requires `RefCell` for mutable external state and can be expensive).
131+
//!
116132
//! Instead, [`Stream::Slice`] was added to track the intended type for parsers to return.
133+
//! If you want to then parse the slice, you then need to take it and turn it back into a
134+
//! [`Stream`].
117135
//!
118136
//! ### `&mut I`
119137
//!
120138
//! `winnow` switched from pure-function parser (`Fn(I) -> (I, O)` to `Fn(&mut I) -> O`).
121139
//! On error, `i` is left pointing at where the error happened.
122140
//!
123-
//! Benefits:
141+
//! Benefits of `Fn(&mut I) -> O`:
124142
//! - Cleaner code: Removes need to pass `i` everywhere and makes changes to `i` more explicit
125143
//! - Correctness: No forgetting to chain `i` through a parser
126144
//! - Flexibility: `I` does not need to be `Copy` or even `Clone`. For example, [`Stateful`] can use `&mut S` instead of `RefCell<S>`.
@@ -130,9 +148,40 @@
130148
//! to the error.
131149
//! See also [#72](https://github.com/winnow-rs/winnow/issues/72).
132150
//!
133-
//! Downsides:
134-
//! - When returning a slice, you have to add a lifetime (`fn foo<'i>(i: &mut &i str) -> ModalResult<&i str>`)
135-
//! - When writing a closure, you need to annotate the type (`|i: &mut _|`, at least the full type isn't needed)
151+
//! Benefits of `Fn(I) -> (I, O)`:
152+
//! - Pure functions can be easier to reason about
153+
//! - Less boilerplate in some situations (see below)
154+
//! - Less syntactic noise in some situations (see below)
155+
//!
156+
//! When returning a slice from the input, you have to add a lifetime:
157+
//! ```rust
158+
//! # use winnow::prelude::*;
159+
//! fn foo<'i>(i: &mut &'i str) -> ModalResult<&'i str> {
160+
//! # Ok("")
161+
//! // ...
162+
//! }
163+
//! ```
164+
//!
165+
//! When writing a closure, you need to annotate the type
166+
//! ```rust
167+
//! # use winnow::prelude::*;
168+
//! # use winnow::combinator::alt;
169+
//! # use winnow::error::ContextError;
170+
//! # let mut input = "";
171+
//! # fn foo<'i>() -> impl ModalParser<&'i str, &'i str, ContextError> {
172+
//! alt((
173+
//! |i: &mut _| {
174+
//! # Ok("")
175+
//! // ...
176+
//! },
177+
//! |i: &mut _| {
178+
//! # Ok("")
179+
//! // ...
180+
//! },
181+
//! ))
182+
//! # }
183+
//! ```
184+
//! *(at least the full type isn't needed)*
136185
//!
137186
//! To save and restore from intermediate states, [`Stream::checkpoint`] and [`Stream::reset`] can help:
138187
//! ```rust

0 commit comments

Comments
 (0)