nom_supreme/
tag.rs

1//! Enhanced tag parser for nom.
2//!
3//! This module introduces a new trait, [`TagError`], and variants of `tag`
4//! and `tag_case_insensitive` that can use the trait to add to an error the
5//! specific tag that was expected. This allows the error message to report
6//! something like `Expected tag: "true"` instead of just `Error: Tag`.
7
8use nom::error::{Error, ErrorKind};
9
10use nom::error::{ParseError, VerboseError};
11
12/// Similar to [`FromExternalError`][nom::error::FromExternalError] and
13/// [`ContextError`][crate::context::ContextError], this trait allows a parser
14/// to create an error representing an unmatched tag. This allows error
15/// messages to produce more useful context about what went wrong.
16pub trait TagError<I, T>: Sized {
17    /// Create an error from an expected tag at a location.
18    fn from_tag(input: I, tag: T) -> Self;
19
20    /// As above, but for a case insensitive tag. By default this just
21    /// calls [`from_tag`][Self::from_tag]
22    fn from_tag_no_case(input: I, tag: T) -> Self {
23        Self::from_tag(input, tag)
24    }
25}
26
27impl<I, T> TagError<I, T> for () {
28    fn from_tag(_input: I, _tag: T) {}
29}
30
31impl<I, T> TagError<I, T> for (I, ErrorKind) {
32    fn from_tag(input: I, _tag: T) -> Self {
33        (input, ErrorKind::Tag)
34    }
35}
36
37impl<I, T> TagError<I, T> for Error<I> {
38    fn from_tag(input: I, _tag: T) -> Self {
39        Error::new(input, ErrorKind::Tag)
40    }
41}
42
43impl<I, T> TagError<I, T> for VerboseError<I> {
44    fn from_tag(input: I, _tag: T) -> Self {
45        VerboseError::from_error_kind(input, ErrorKind::Tag)
46    }
47}
48
49/// Complete input version of enhanced `tag` parsers
50pub mod complete {
51    use nom::{Compare, CompareResult, Err, IResult, InputLength, InputTake};
52
53    use super::TagError;
54
55    /// Parser recognizing a fixed pattern, called a tag. If the front of the
56    /// input data matches the `tag`, that part of the input will be returned.
57    /// Records the tag in the error in the event of a parse failure via
58    /// [`TagError`].
59    ///
60    /// # Example
61    ///
62    /// ```
63    /// # use nom::{IResult, Err, Needed};
64    /// use cool_asserts::assert_matches;
65    /// use nom_supreme::tag::complete::tag;
66    /// use nom_supreme::error::{ErrorTree, BaseErrorKind, Expectation};
67    ///
68    /// fn parse_hello(s: &str) -> IResult<&str, &str, ErrorTree<&str>> {
69    ///     tag("hello")(s)
70    /// }
71    ///
72    /// assert_matches!(
73    ///     parse_hello("hello, world!"),
74    ///     Ok((", world!", "hello")),
75    /// );
76    ///
77    /// assert_matches!(
78    ///     parse_hello("something"),
79    ///     Err(Err::Error(ErrorTree::Base {
80    ///         location: "something",
81    ///         kind: BaseErrorKind::Expected(Expectation::Tag("hello")),
82    ///     }))
83    /// );
84    ///
85    /// assert_matches!(
86    ///     parse_hello("hel"),
87    ///     Err(Err::Error(ErrorTree::Base {
88    ///         location: "hel",
89    ///         kind: BaseErrorKind::Expected(Expectation::Tag("hello")),
90    ///     }))
91    /// );
92    /// ```
93    pub fn tag<T, I, E>(tag: T) -> impl Clone + Fn(I) -> IResult<I, I, E>
94    where
95        T: InputLength + Clone,
96        I: InputTake + Compare<T>,
97        E: TagError<I, T>,
98    {
99        let tag_len = tag.input_len();
100
101        move |input: I| match input.compare(tag.clone()) {
102            CompareResult::Ok => Ok(input.take_split(tag_len)),
103            _ => Err(Err::Error(E::from_tag(input, tag.clone()))),
104        }
105    }
106
107    /// Parser recognizing a fixed pattern, called a tag. If the front of the
108    /// input data matches the `tag`, case insensitively, that part of the
109    /// input will be returned. Records the tag in the error in the event of a
110    /// parse failure via [`TagError`].
111    ///
112    /// # Example
113    ///
114    /// ```
115    /// # use nom::{IResult, Err, Needed};
116    /// use cool_asserts::assert_matches;
117    /// use nom_supreme::tag::complete::tag_no_case;
118    /// use nom_supreme::error::{ErrorTree, BaseErrorKind, Expectation};
119    ///
120    /// fn parse_hello(s: &str) -> IResult<&str, &str, ErrorTree<&str>> {
121    ///     tag_no_case("hello")(s)
122    /// }
123    ///
124    /// assert_matches!(
125    ///     parse_hello("HELLO, WORLD!"),
126    ///     Ok((", WORLD!", "HELLO")),
127    /// );
128    ///
129    /// assert_matches!(
130    ///     parse_hello("something"),
131    ///     Err(Err::Error(ErrorTree::Base {
132    ///         location: "something",
133    ///         kind: BaseErrorKind::Expected(Expectation::Tag("hello")),
134    ///     }))
135    /// );
136    ///
137    /// assert_matches!(
138    ///     parse_hello("HEL"),
139    ///     Err(Err::Error(ErrorTree::Base {
140    ///         location: "HEL",
141    ///         kind: BaseErrorKind::Expected(Expectation::Tag("hello")),
142    ///     }))
143    /// );
144    /// ```
145    pub fn tag_no_case<T, I, E>(tag: T) -> impl Clone + Fn(I) -> IResult<I, I, E>
146    where
147        T: InputLength + Clone,
148        I: InputTake + Compare<T>,
149        E: TagError<I, T>,
150    {
151        move |input: I| match input.compare_no_case(tag.clone()) {
152            CompareResult::Ok => Ok(input.take_split(tag.input_len())),
153            _ => Err(Err::Error(E::from_tag_no_case(input, tag.clone()))),
154        }
155    }
156}
157
158/// Streaming version of enhanced `tag` parsers.
159pub mod streaming {
160    use nom::{Compare, CompareResult, Err, IResult, InputLength, InputTake, Needed};
161
162    use super::TagError;
163
164    /// Parser recognizing a fixed pattern, called a tag. If the front of the
165    /// input data matches the `tag`, that part of the input will be returned.
166    /// Records the tag in the error in the event of a parse failure via
167    /// [`TagError`]. If there is only a partial match, returns
168    /// [`Err::Incomplete`][nom::Err::Incomplete].
169    ///
170    /// # Example
171    ///
172    /// ```
173    /// # use nom::{IResult, Err, Needed};
174    /// use cool_asserts::assert_matches;
175    /// use nom_supreme::tag::streaming::tag;
176    /// use nom_supreme::error::{ErrorTree, BaseErrorKind, Expectation};
177    ///
178    /// fn parse_hello(s: &str) -> IResult<&str, &str, ErrorTree<&str>> {
179    ///     tag("hello")(s)
180    /// }
181    ///
182    /// assert_matches!(
183    ///     parse_hello("hello, world!"),
184    ///     Ok((", world!", "hello")),
185    /// );
186    ///
187    /// assert_matches!(
188    ///     parse_hello("something"),
189    ///     Err(Err::Error(ErrorTree::Base {
190    ///         location: "something",
191    ///         kind: BaseErrorKind::Expected(Expectation::Tag("hello")),
192    ///     }))
193    /// );
194    ///
195    /// assert_matches!(
196    ///     parse_hello("hel"),
197    ///     Err(Err::Incomplete(Needed::Size(n))) if n.get() == 2,
198    /// );
199    /// ```
200    pub fn tag<T, I, E>(tag: T) -> impl Clone + Fn(I) -> IResult<I, I, E>
201    where
202        T: InputLength + Clone,
203        I: InputLength + InputTake + Compare<T>,
204        E: TagError<I, T>,
205    {
206        let tag_len = tag.input_len();
207
208        move |input: I| match input.compare(tag.clone()) {
209            CompareResult::Ok => Ok(input.take_split(tag_len)),
210            CompareResult::Incomplete => {
211                Err(Err::Incomplete(Needed::new(tag_len - input.input_len())))
212            }
213            CompareResult::Error => Err(Err::Error(E::from_tag(input, tag.clone()))),
214        }
215    }
216
217    /// Parser recognizing a fixed pattern, called a tag. If the front of the
218    /// input data matches the `tag`, case insensitively, that part of the
219    /// input will be returned. Records the tag in the error in the event of a
220    /// parse failure via [`TagError`]. If there is only a partial match,
221    /// returns [`Err::Incomplete`][[nom::Err::Incomplete]].
222    ///
223    /// # Example
224    ///
225    /// ```
226    /// # use nom::{IResult, Err, Needed};
227    /// use cool_asserts::assert_matches;
228    /// use nom_supreme::tag::streaming::tag_no_case;
229    /// use nom_supreme::error::{ErrorTree, BaseErrorKind, Expectation};
230    ///
231    /// fn parse_hello(s: &str) -> IResult<&str, &str, ErrorTree<&str>> {
232    ///     tag_no_case("hello")(s)
233    /// }
234    ///
235    /// assert_matches!(
236    ///     parse_hello("HELLO, WORLD!"),
237    ///     Ok((", WORLD!", "HELLO")),
238    /// );
239    ///
240    /// assert_matches!(
241    ///     parse_hello("something"),
242    ///     Err(Err::Error(ErrorTree::Base {
243    ///         location: "something",
244    ///         kind: BaseErrorKind::Expected(Expectation::Tag("hello")),
245    ///     }))
246    /// );
247    ///
248    /// assert_matches!(
249    ///     parse_hello("HEL"),
250    ///     Err(Err::Incomplete(Needed::Size(n))) if n.get() == 2,
251    /// );
252    /// ```
253    pub fn tag_no_case<T, I, E>(tag: T) -> impl Clone + Fn(I) -> IResult<I, I, E>
254    where
255        T: InputLength + Clone,
256        I: InputLength + InputTake + Compare<T>,
257        E: TagError<I, T>,
258    {
259        let tag_len = tag.input_len();
260
261        move |input: I| match input.compare_no_case(tag.clone()) {
262            CompareResult::Ok => Ok(input.take_split(tag_len)),
263            CompareResult::Incomplete => {
264                Err(Err::Incomplete(Needed::new(tag_len - input.input_len())))
265            }
266            CompareResult::Error => Err(Err::Error(E::from_tag_no_case(input, tag.clone()))),
267        }
268    }
269}