Skip to main content

time/format_description/parse/
mod.rs

1//! Parser for format descriptions.
2
3use alloc::vec::Vec;
4
5use self::lexer_ast::parse_generic;
6use self::sealed::{Version, VersionedParser};
7pub use self::strftime::{parse_strftime_borrowed, parse_strftime_owned};
8use crate::error;
9use crate::format_description::{BorrowedFormatItem, FormatDescriptionV3, OwnedFormatItem};
10
11macro_rules! version {
12    ($pat:pat) => {
13        const { matches!(VERSION, $pat) }
14    };
15}
16
17macro_rules! assert_version {
18    () => {
19        const {
20            assert!(matches!(VERSION, 1..=3), "invalid version provided");
21        }
22    };
23}
24
25mod format_item;
26mod lexer_ast;
27mod strftime;
28
29mod sealed {
30    use super::*;
31
32    /// The version of the parser, represented in the type system.
33    #[expect(
34        missing_debug_implementations,
35        reason = "only used at the type level; not public API"
36    )]
37    pub struct Version<const N: usize>;
38
39    /// A trait for parsing format descriptions, with different output types depending on the
40    /// version.
41    pub trait VersionedParser {
42        /// The output type of the borrowed parser. This type avoids allocating where possible.
43        type BorrowedOutput<'input>;
44
45        /// The output type of the owned parser. This type may allocate but is valid for `'static`.
46        type OwnedOutput;
47
48        /// Parse a format description into a type that avoids allocating where possible.
49        fn parse_borrowed(
50            s: &str,
51        ) -> Result<Self::BorrowedOutput<'_>, error::InvalidFormatDescription>;
52
53        /// Parse a format description into an owned type, which may allocate but is valid for
54        /// `'static`.
55        fn parse_owned(s: &str) -> Result<Self::OwnedOutput, error::InvalidFormatDescription>;
56    }
57}
58
59impl VersionedParser for Version<1> {
60    type BorrowedOutput<'input> = Vec<BorrowedFormatItem<'input>>;
61    type OwnedOutput = OwnedFormatItem;
62
63    #[inline]
64    fn parse_borrowed(
65        s: &str,
66    ) -> Result<Self::BorrowedOutput<'_>, error::InvalidFormatDescription> {
67        Ok(parse_generic::<1, false>(s)?)
68    }
69
70    #[inline]
71    fn parse_owned(s: &str) -> Result<Self::OwnedOutput, error::InvalidFormatDescription> {
72        Ok(parse_generic::<1, true>(s)?)
73    }
74}
75
76impl VersionedParser for Version<2> {
77    type BorrowedOutput<'input> = Vec<BorrowedFormatItem<'input>>;
78    type OwnedOutput = OwnedFormatItem;
79
80    #[inline]
81    fn parse_borrowed(
82        s: &str,
83    ) -> Result<Self::BorrowedOutput<'_>, error::InvalidFormatDescription> {
84        Ok(parse_generic::<2, false>(s)?)
85    }
86
87    #[inline]
88    fn parse_owned(s: &str) -> Result<Self::OwnedOutput, error::InvalidFormatDescription> {
89        Ok(parse_generic::<2, true>(s)?)
90    }
91}
92
93impl VersionedParser for Version<3> {
94    type BorrowedOutput<'input> = FormatDescriptionV3<'input>;
95    type OwnedOutput = FormatDescriptionV3<'static>;
96
97    #[inline]
98    fn parse_borrowed(
99        s: &str,
100    ) -> Result<Self::BorrowedOutput<'_>, error::InvalidFormatDescription> {
101        Ok(parse_generic::<3, false>(s)?)
102    }
103
104    #[inline]
105    fn parse_owned(s: &str) -> Result<Self::OwnedOutput, error::InvalidFormatDescription> {
106        Ok(parse_generic::<3, true>(s)?)
107    }
108}
109
110/// Parse a sequence of items from the format description.
111///
112/// The syntax for the format description can be found in [the
113/// book](https://time-rs.github.io/book/api/format-description.html).
114///
115/// This function exists for backward compatibility reasons. It is equivalent to calling
116/// `parse_borrowed::<1>(s)`. **It is recommended to use version 3, not version 1.**
117#[deprecated(
118    since = "0.3.48",
119    note = "use `parse_borrowed` with the appropriate version for clarity"
120)]
121#[inline]
122pub fn parse(s: &str) -> Result<Vec<BorrowedFormatItem<'_>>, error::InvalidFormatDescription> {
123    parse_borrowed::<1>(s)
124}
125
126/// Parse a sequence of items from the format description.
127///
128/// The syntax for the format description can be found in [the
129/// book](https://time-rs.github.io/book/api/format-description.html). The version of the format
130/// description is provided as the const parameter. **It is recommended to use version 3.**
131///
132/// # Return type
133///
134/// The return type of this function depends on the version provided.
135///
136/// - For versions 1 and 2, the function returns `Result<Vec<BorrowedFormatItem<'_>>,
137///   InvalidFormatDescription>`.
138/// - For version 3, the function returns `Result<FormatDescriptionV3<'_>,
139///   InvalidFormatDescription>`.
140#[inline]
141pub fn parse_borrowed<const VERSION: usize>(
142    s: &str,
143) -> Result<
144    <Version<VERSION> as VersionedParser>::BorrowedOutput<'_>,
145    error::InvalidFormatDescription,
146>
147where
148    Version<VERSION>: VersionedParser,
149{
150    Version::<VERSION>::parse_borrowed(s)
151}
152
153/// Parse a sequence of items from the format description.
154///
155/// The syntax for the format description can be found in [the
156/// book](https://time-rs.github.io/book/api/format-description.html). The version of the format
157/// description is provided as the const parameter.
158///
159/// Unlike [`parse`], this function returns [`OwnedFormatItem`], which owns its contents. This means
160/// that there is no lifetime that needs to be handled. **It is recommended to use version 3.**
161///
162/// # Return type
163///
164/// The return type of this function depends on the version provided.
165///
166/// - For versions 1 and 2, the function returns `Result<OwnedFormatItem,
167///   InvalidFormatDescription>`.
168/// - For version 3, the function returns `Result<FormatDescriptionV3<'static>,
169///   InvalidFormatDescription>`.
170///
171/// [`OwnedFormatItem`]: crate::format_description::OwnedFormatItem
172#[inline]
173pub fn parse_owned<const VERSION: usize>(
174    s: &str,
175) -> Result<<Version<VERSION> as VersionedParser>::OwnedOutput, error::InvalidFormatDescription>
176where
177    Version<VERSION>: VersionedParser,
178{
179    Version::<VERSION>::parse_owned(s)
180}
181
182/// A location within a string.
183#[derive(Clone, Copy)]
184struct Location {
185    /// The zero-indexed byte of the string.
186    byte: u32,
187}
188
189impl Location {
190    const DUMMY: Self = Self { byte: u32::MAX };
191
192    /// Create a new [`Span`] from `self` to `other`.
193    #[inline]
194    const fn to(self, end: Self) -> Span {
195        Span { start: self, end }
196    }
197
198    /// Create a new [`Span`] consisting entirely of `self`.
199    #[inline]
200    const fn to_self(self) -> Span {
201        Span {
202            start: self,
203            end: self,
204        }
205    }
206
207    #[inline]
208    const fn with_length(self, length: usize) -> Span {
209        Span {
210            start: self,
211            end: Self {
212                byte: self.byte + length as u32 - 1,
213            },
214        }
215    }
216
217    /// Offset the location by the provided amount.
218    ///
219    /// Note that this assumes the resulting location is on the same line as the original location.
220    #[must_use = "this does not modify the original value"]
221    #[inline]
222    const fn offset(&self, offset: u32) -> Self {
223        Self {
224            byte: self.byte + offset,
225        }
226    }
227
228    /// Create an error with the provided message at this location.
229    #[inline]
230    const fn error(self, message: &'static str) -> ErrorInner {
231        ErrorInner {
232            _message: message,
233            _span: Span {
234                start: self,
235                end: self,
236            },
237        }
238    }
239}
240
241/// A value with an associated [`Location`].
242#[derive(Clone, Copy)]
243struct WithLocation<T> {
244    /// The value.
245    value: T,
246    /// Where the value was in the format string.
247    location: Location,
248}
249
250impl<T> core::ops::Deref for WithLocation<T> {
251    type Target = T;
252
253    #[inline]
254    fn deref(&self) -> &Self::Target {
255        &self.value
256    }
257}
258
259/// Helper trait to attach a [`Location`] to a value.
260trait WithLocationValue: Sized {
261    /// Attach a [`Location`] to a value.
262    fn with_location(self, location: Location) -> WithLocation<Self>;
263}
264
265impl<T> WithLocationValue for T {
266    #[inline]
267    fn with_location(self, location: Location) -> WithLocation<Self> {
268        WithLocation {
269            value: self,
270            location,
271        }
272    }
273}
274
275/// A start and end point within a string.
276#[derive(Clone, Copy)]
277struct Span {
278    start: Location,
279    end: Location,
280}
281
282impl Span {
283    const DUMMY: Self = Self {
284        start: Location { byte: u32::MAX },
285        end: Location { byte: u32::MAX },
286    };
287
288    /// Obtain a `Span` pointing at the start of the pre-existing span.
289    #[must_use = "this does not modify the original value"]
290    #[inline]
291    const fn shrink_to_start(&self) -> Self {
292        Self {
293            start: self.start,
294            end: self.start,
295        }
296    }
297
298    /// Obtain a `Span` pointing at the end of the pre-existing span.
299    #[must_use = "this does not modify the original value"]
300    const fn shrink_to_end(&self) -> Self {
301        Self {
302            start: self.end,
303            end: self.end,
304        }
305    }
306
307    /// Create an error with the provided message at this span.
308    #[inline]
309    const fn error(self, message: &'static str) -> ErrorInner {
310        ErrorInner {
311            _message: message,
312            _span: self,
313        }
314    }
315}
316
317/// A value with an associated [`Span`].
318#[derive(Clone, Copy)]
319struct Spanned<T> {
320    /// The value.
321    value: T,
322    /// Where the value was in the format string.
323    span: Span,
324}
325
326impl<T> core::ops::Deref for Spanned<T> {
327    type Target = T;
328
329    #[inline]
330    fn deref(&self) -> &Self::Target {
331        &self.value
332    }
333}
334
335impl<T> Spanned<T> {
336    #[inline]
337    fn map<F, U>(self, f: F) -> Spanned<U>
338    where
339        F: FnOnce(T) -> U,
340    {
341        Spanned {
342            value: f(self.value),
343            span: self.span,
344        }
345    }
346}
347
348trait OptionExt<T> {
349    fn transpose(self) -> Spanned<Option<T>>;
350}
351
352impl<T> OptionExt<T> for Option<Spanned<T>> {
353    #[inline]
354    fn transpose(self) -> Spanned<Option<T>> {
355        match self {
356            Some(spanned) => Spanned {
357                value: Some(spanned.value),
358                span: spanned.span,
359            },
360            None => Spanned {
361                value: None,
362                span: Span::DUMMY,
363            },
364        }
365    }
366}
367
368/// Helper trait to attach a [`Span`] to a value.
369trait SpannedValue: Sized {
370    /// Attach a [`Span`] to a value.
371    fn spanned(self, span: Span) -> Spanned<Self>;
372}
373
374impl<T> SpannedValue for T {
375    #[inline]
376    fn spanned(self, span: Span) -> Spanned<Self> {
377        Spanned { value: self, span }
378    }
379}
380
381/// The internal error type.
382struct ErrorInner {
383    /// The message displayed to the user.
384    _message: &'static str,
385    /// Where the error originated.
386    _span: Span,
387}
388
389/// A complete error description.
390struct Error {
391    /// The internal error.
392    _inner: Unused<ErrorInner>,
393    /// The error needed for interoperability with the rest of `time`.
394    public: error::InvalidFormatDescription,
395}
396
397impl From<Error> for error::InvalidFormatDescription {
398    #[inline]
399    fn from(error: Error) -> Self {
400        error.public
401    }
402}
403
404impl From<core::convert::Infallible> for Error {
405    #[inline]
406    fn from(v: core::convert::Infallible) -> Self {
407        match v {}
408    }
409}
410
411/// A value that may be used in the future, but currently is not.
412///
413/// This struct exists so that data can semantically be passed around without _actually_ passing it
414/// around. This way the data still exists if it is needed in the future.
415// `PhantomData` is not used directly because we don't want to introduce any trait implementations.
416struct Unused<T>(core::marker::PhantomData<T>);
417
418/// Indicate that a value is currently unused.
419#[inline]
420fn unused<T>(_: T) -> Unused<T> {
421    Unused(core::marker::PhantomData)
422}