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    /// Create a new [`Span`] from `self` to `other`.
211    #[inline]
212    const fn to(self, end: Self) -> Span {
213        Span { start: self, end }
214    }
215
216    /// Create a new [`Span`] consisting entirely of `self`.
217    #[inline]
218    const fn to_self(self) -> Span {
219        Span {
220            start: self,
221            end: self,
222        }
223    }
224
225    /// Offset the location by the provided amount.
226    ///
227    /// Note that this assumes the resulting location is on the same line as the original location.
228    #[must_use = "this does not modify the original value"]
229    #[inline]
230    const fn offset(&self, offset: u32) -> Self {
231        Self {
232            byte: self.byte + offset,
233        }
234    }
235
236    /// Create an error with the provided message at this location.
237    #[inline]
238    const fn error(self, message: &'static str) -> ErrorInner {
239        ErrorInner {
240            _message: message,
241            _span: Span {
242                start: self,
243                end: self,
244            },
245        }
246    }
247}
248
249/// A start and end point within a string.
250#[derive(Clone, Copy)]
251struct Span {
252    start: Location,
253    end: Location,
254}
255
256impl Span {
257    const DUMMY: Self = Self {
258        start: Location { byte: u32::MAX },
259        end: Location { byte: u32::MAX },
260    };
261
262    /// Obtain a `Span` pointing at the start of the pre-existing span.
263    #[must_use = "this does not modify the original value"]
264    #[inline]
265    const fn shrink_to_start(&self) -> Self {
266        Self {
267            start: self.start,
268            end: self.start,
269        }
270    }
271
272    /// Obtain a `Span` pointing at the end of the pre-existing span.
273    #[must_use = "this does not modify the original value"]
274    const fn shrink_to_end(&self) -> Self {
275        Self {
276            start: self.end,
277            end: self.end,
278        }
279    }
280
281    /// Create an error with the provided message at this span.
282    #[inline]
283    const fn error(self, message: &'static str) -> ErrorInner {
284        ErrorInner {
285            _message: message,
286            _span: self,
287        }
288    }
289}
290
291/// A value with an associated [`Span`].
292#[derive(Clone, Copy)]
293struct Spanned<T> {
294    /// The value.
295    value: T,
296    /// Where the value was in the format string.
297    span: Span,
298}
299
300impl<T> core::ops::Deref for Spanned<T> {
301    type Target = T;
302
303    #[inline]
304    fn deref(&self) -> &Self::Target {
305        &self.value
306    }
307}
308
309impl<T> Spanned<T> {
310    #[inline]
311    fn map<F, U>(self, f: F) -> Spanned<U>
312    where
313        F: FnOnce(T) -> U,
314    {
315        Spanned {
316            value: f(self.value),
317            span: self.span,
318        }
319    }
320}
321
322trait OptionExt<T> {
323    fn transpose(self) -> Spanned<Option<T>>;
324}
325
326impl<T> OptionExt<T> for Option<Spanned<T>> {
327    #[inline]
328    fn transpose(self) -> Spanned<Option<T>> {
329        match self {
330            Some(spanned) => Spanned {
331                value: Some(spanned.value),
332                span: spanned.span,
333            },
334            None => Spanned {
335                value: None,
336                span: Span::DUMMY,
337            },
338        }
339    }
340}
341
342/// Helper trait to attach a [`Span`] to a value.
343trait SpannedValue: Sized {
344    /// Attach a [`Span`] to a value.
345    fn spanned(self, span: Span) -> Spanned<Self>;
346}
347
348impl<T> SpannedValue for T {
349    #[inline]
350    fn spanned(self, span: Span) -> Spanned<Self> {
351        Spanned { value: self, span }
352    }
353}
354
355/// The internal error type.
356struct ErrorInner {
357    /// The message displayed to the user.
358    _message: &'static str,
359    /// Where the error originated.
360    _span: Span,
361}
362
363/// A complete error description.
364struct Error {
365    /// The internal error.
366    _inner: Unused<ErrorInner>,
367    /// The error needed for interoperability with the rest of `time`.
368    public: error::InvalidFormatDescription,
369}
370
371impl From<Error> for error::InvalidFormatDescription {
372    #[inline]
373    fn from(error: Error) -> Self {
374        error.public
375    }
376}
377
378/// A value that may be used in the future, but currently is not.
379///
380/// This struct exists so that data can semantically be passed around without _actually_ passing it
381/// around. This way the data still exists if it is needed in the future.
382// `PhantomData` is not used directly because we don't want to introduce any trait implementations.
383struct Unused<T>(core::marker::PhantomData<T>);
384
385/// Indicate that a value is currently unused.
386#[inline]
387fn unused<T>(_: T) -> Unused<T> {
388    Unused(core::marker::PhantomData)
389}