Skip to main content

time/format_description/parse/
mod.rs

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