alpm_parsers/traits.rs
1//! Traits used for the parsers.
2
3use winnow::{
4 ModalResult,
5 Parser,
6 ascii::line_ending,
7 combinator::{alt, eof, peek, terminated},
8 error::{ContextError, ErrMode},
9};
10
11/// Parses either a line ending or eof from an `input`.
12///
13/// # Errors
14///
15/// Returns an error if `input` contains neither a line ending nor eof.
16fn line_ending_or_eof<'a>(input: &mut &'a str) -> ModalResult<&'a str> {
17 alt((line_ending, eof)).parse_next(input)
18}
19
20/// A trait for types that implement a parser.
21///
22/// The [`AlpmParser::parser`] function is expected to only consume the characters that are part of
23/// its own structure/format.
24///
25/// For checks, such as "parse till EOF" or "parse until delimiter", check the [`ParserUntil`] and
26/// [`ParserUntilInclusive`] traits, which provide auto implementations for [`AlpmParser`].
27///
28/// # Examples
29///
30/// ```rust
31/// use alpm_parsers::traits::AlpmParser;
32/// use winnow::{
33/// ModalResult,
34/// Parser,
35/// error::{ContextError, ErrMode, StrContext},
36/// token::take_while,
37/// };
38///
39/// # fn main() -> testresult::TestResult {
40/// #[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
41/// struct Alphanumeric(String);
42///
43/// impl AlpmParser for Alphanumeric {
44/// fn parser(input: &mut &str) -> ModalResult<Self> {
45/// take_while(1.., |c: char| c.is_alphanumeric())
46/// .map(|s: &str| Alphanumeric(s.to_string()))
47/// .parse_next(input)
48/// }
49/// }
50///
51/// assert_eq!(
52/// Alphanumeric::parser.parse_peek("abc123\nnext"),
53/// Ok(("\nnext", Alphanumeric("abc123".to_string())))
54/// );
55/// # Ok(())
56/// # }
57/// ```
58pub trait AlpmParser: Sized {
59 /// Returns a [`Parser`] that parses `Self` from the given input stream.
60 ///
61 /// This parser is expected to not consume anything but the tokens needed to parse and build
62 /// `Self`.
63 fn parser(input: &mut &str) -> ModalResult<Self>;
64
65 /// Attaches a winnow error context to the parser when a parse error is encountered.
66 ///
67 /// # Note
68 ///
69 /// This is useful to, for example, show consistent error messages whenever a character is
70 /// encountered that should not be in the character set of the value to parse (see
71 /// [`ParserUntil::parser_until`]).
72 fn delimiter_error_context<'a, O, P>(
73 parser: P,
74 ) -> impl Parser<&'a str, O, ErrMode<ContextError>>
75 where
76 P: Parser<&'a str, O, ErrMode<ContextError>>,
77 {
78 parser
79 }
80}
81
82/// A trait for types that can be parsed until a given delimiter parser matches.
83///
84/// Allows wrapping non-delimiter-aware type parsers, to make them delimiter-aware for inline usage
85/// in file parsers. Type parsers, such as `PackageVersion`, do not have any knowledge about the
86/// surrounding file format or context during parsing. This trait allows type parsers to stay pure
87/// and agnostic to the surrounding format, while allowing file format parsers to specify a
88/// delimiter, such as `\n` or `,`, providing the necessary context to parse the type inside the
89/// format in question.
90///
91/// The `ParserUntil` trait is auto-implemented for every type implementing [`AlpmParser`].
92///
93/// The [`ParserUntilInclusive`] trait is auto-implemented for every type implementing
94/// `ParserUntil` and provides functions to also consume the specified delimiter.
95///
96/// # Note
97///
98/// Produced parsers are expected to **NOT** consume the delimiter.
99/// To properly enforce this constraint several conventions must be upheld:
100///
101/// - Delimiters must not be part of the allowed set of tokens. Otherwise there's no way to
102/// distinguish between delimiter and content.
103/// - In the case that a file format supports escaping, that file format must implement their own
104/// escaping-aware variants of parsers for the affected types.
105///
106/// # Examples
107///
108/// ```rust
109/// use alpm_parsers::traits::{AlpmParser, ParserUntil};
110/// use winnow::{
111/// ModalResult,
112/// Parser,
113/// error::{ContextError, ErrMode, StrContext},
114/// token::take_while,
115/// };
116///
117/// # fn main() -> testresult::TestResult {
118/// #[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
119/// struct Alphanumeric(String);
120///
121/// // Implementing `AlpmParser` automatically implements `ParserUntil`.
122/// impl AlpmParser for Alphanumeric {
123/// fn parser(input: &mut &str) -> ModalResult<Self> {
124/// take_while(1.., |c: char| c.is_alphanumeric())
125/// .map(|s: &str| Alphanumeric(s.to_string()))
126/// .parse_next(input)
127/// }
128///
129/// fn delimiter_error_context<'a, O, P>(
130/// parser: P,
131/// ) -> impl Parser<&'a str, O, ErrMode<ContextError>>
132/// where
133/// P: Parser<&'a str, O, ErrMode<ContextError>>,
134/// {
135/// parser.context(StrContext::Label("alphanumeric characters"))
136/// }
137/// }
138///
139/// // The parser succeeds with a alphanumeric string that is followed by a newline.
140/// assert_eq!(
141/// Alphanumeric::parser_until_line_ending.parse_peek("abc123\nnext"),
142/// Ok(("\nnext", Alphanumeric("abc123".to_string())))
143/// );
144///
145/// // An alphanumeric string followed by a non-alphanumeric character that isn't a
146/// // newline will fail.
147/// assert!(matches!(
148/// Alphanumeric::parser_until_line_ending.parse_peek("abc123{\nnext"),
149/// Err(_),
150/// ));
151///
152/// // If we expect it to be a `{` though, it works just as expected.
153/// assert_eq!(
154/// Alphanumeric::parser_until("{").parse_peek("abc123{\nnext"),
155/// Ok(("{\nnext", Alphanumeric("abc123".to_string())))
156/// );
157///
158/// // Parsing the full string with `parser_until_eof` works as expected.
159/// assert_eq!(
160/// Alphanumeric::parser_until_eof.parse_peek("abc123"),
161/// Ok(("", Alphanumeric("abc123".to_string())))
162/// );
163///
164/// // If there's anything but the end of the input, the parser fails.
165/// assert!(matches!(
166/// Alphanumeric::parser_until_eof.parse_peek("abc123_"),
167/// Err(_)
168/// ));
169/// # Ok(())
170/// # }
171/// ```
172pub trait ParserUntil: Sized {
173 /// Returns a [`Parser`] that parses `Self` until the given `delimiter` parser
174 /// matches.
175 ///
176 /// # Note
177 ///
178 /// Does not consume the `delimiter`.
179 fn parser_until<'a, P>(delimiter: P) -> impl Parser<&'a str, Self, ErrMode<ContextError>>
180 where
181 P: Parser<&'a str, &'a str, ErrMode<ContextError>>;
182
183 /// Returns a [`Parser`] that parses an entire `input`.
184 ///
185 /// Delegates to [`Self::parser_until`] with the [`eof`] delimiter.
186 ///
187 /// # Note
188 ///
189 /// The returned [`Parser`] is required to fully consume `input`.
190 #[inline]
191 fn parser_until_eof(input: &mut &str) -> ModalResult<Self> {
192 Self::parser_until(eof).parse_next(input)
193 }
194
195 /// Returns a [`Parser`] that parses until the end of line or [`eof`].
196 ///
197 /// Delegates to [`Self::parser_until`] with the [`line_ending`] or [`eof`] delimiter.
198 /// A line ending is either `\n` or `\r\n`.
199 ///
200 /// # Note
201 ///
202 /// The line ending is not consumed.
203 #[inline]
204 fn parser_until_line_ending(input: &mut &str) -> ModalResult<Self> {
205 Self::parser_until(line_ending_or_eof).parse_next(input)
206 }
207}
208
209impl<U: AlpmParser> ParserUntil for U {
210 /// Returns a [`Parser`] that parses `Self` until the given `delimiter` parser
211 /// matches.
212 ///
213 /// # Note
214 ///
215 /// Does not consume the delimiter.
216 fn parser_until<'a, P>(delimiter: P) -> impl Parser<&'a str, Self, ErrMode<ContextError>>
217 where
218 P: Parser<&'a str, &'a str, ErrMode<ContextError>>,
219 {
220 terminated(U::parser, Self::delimiter_error_context(peek(delimiter)))
221 }
222}
223
224/// A trait for types that can be parsed until a given delimiter parser matches, while consuming
225/// that delimiter.
226///
227/// Allows creating non-delimiter-aware type parsers, that can be used inline in file parsers.
228///
229/// Automatically implemented for all types implementing [`ParserUntil`] and thereby transitively
230/// [`AlpmParser`].
231///
232/// # Examples
233///
234/// ```rust
235/// use alpm_parsers::traits::{AlpmParser, ParserUntil, ParserUntilInclusive};
236/// use winnow::{
237/// ModalResult,
238/// Parser,
239/// error::{ContextError, ErrMode},
240/// token::take_while,
241/// };
242///
243/// # fn main() -> testresult::TestResult {
244/// #[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
245/// struct Alphanumeric(String);
246///
247/// // Implementing `AlpmParser` automatically implements `ParserUntilInclusive`:
248/// // `ParserUntil` is auto-implemented over `AlpmParser` and `ParserUntilInclusive` is auto-implemented over `ParserUntil`.
249/// impl AlpmParser for Alphanumeric {
250/// fn parser(input: &mut &str) -> ModalResult<Self> {
251/// take_while(1.., |c: char| c.is_alphanumeric())
252/// .map(|s: &str| Alphanumeric(s.to_string()))
253/// .parse_next(input)
254/// }
255/// }
256///
257/// assert_eq!(
258/// Alphanumeric::parser_until_line_ending_inclusive.parse_peek("abc123\nnext"),
259/// Ok(("next", Alphanumeric("abc123".to_string())))
260/// );
261///
262/// # Ok(())
263/// # }
264/// ```
265pub trait ParserUntilInclusive: Sized {
266 /// Returns a [`Parser`] that parses the whole input and consumes the given `delimiter` parser.
267 fn parser_until_inclusive<'a, P>(
268 delimiter: P,
269 ) -> impl Parser<&'a str, Self, ErrMode<ContextError>>
270 where
271 P: Parser<&'a str, &'a str, ErrMode<ContextError>>;
272
273 /// Returns a [`Parser`] that parses until the end of line and fully consumes it.
274 ///
275 /// Delegates to [`Self::parser_until_inclusive`] with the [`line_ending`] and [`eof`]
276 /// delimiter.
277 ///
278 /// A line ending is `\n`, `\r\n` or [`eof`] and consumed as well.
279 #[inline]
280 fn parser_until_line_ending_inclusive(input: &mut &str) -> ModalResult<Self> {
281 Self::parser_until_inclusive(line_ending_or_eof).parse_next(input)
282 }
283}
284
285impl<U: ParserUntil> ParserUntilInclusive for U {
286 /// Returns a [`Parser`] that parses the whole input and consumes the given `delimiter` parser.
287 ///
288 /// Delegates to [`ParserUntil::parser_until`] and consumes the delimiter.
289 #[inline]
290 fn parser_until_inclusive<'a, P>(
291 delimiter: P,
292 ) -> impl Parser<&'a str, Self, ErrMode<ContextError>>
293 where
294 P: Parser<&'a str, &'a str, ErrMode<ContextError>>,
295 {
296 // The delimiter is moved into the closure and borrowed via `by_ref()` on each call.
297 let mut delimiter = delimiter;
298 move |input: &mut &'a str| {
299 let parsed = U::parser_until(delimiter.by_ref()).parse_next(input)?;
300 // Since `U::parser_until` succeeded, we **know** that the delimiter exists.
301 // The following does thereby not fail and does not need context.
302 let _ = delimiter.by_ref().parse_next(input)?;
303 Ok(parsed)
304 }
305 }
306}