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