Skip to main content

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}