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