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}