time/format_description/parse/
mod.rs

1//! Parser for format descriptions.
2
3use alloc::boxed::Box;
4use alloc::vec::Vec;
5
6use crate::{error, format_description};
7
8/// A helper macro to make version restrictions simpler to read and write.
9macro_rules! version {
10    ($range:expr) => {
11        $range.contains(&VERSION)
12    };
13}
14
15/// A helper macro to statically validate the version (when used as a const parameter).
16macro_rules! validate_version {
17    ($version:ident) => {
18        #[allow(clippy::let_unit_value)]
19        let _ = $crate::format_description::parse::Version::<$version>::IS_VALID;
20    };
21}
22
23mod ast;
24mod format_item;
25mod lexer;
26
27/// A struct that is used to ensure that the version is valid.
28struct Version<const N: usize>;
29impl<const N: usize> Version<N> {
30    /// A constant that panics if the version is not valid. This results in a post-monomorphization
31    /// error.
32    const IS_VALID: () = assert!(N >= 1 && N <= 2);
33}
34
35/// Parse a sequence of items from the format description.
36///
37/// The syntax for the format description can be found in [the
38/// book](https://time-rs.github.io/book/api/format-description.html).
39///
40/// This function exists for backward compatibility reasons. It is equivalent to calling
41/// `parse_borrowed::<1>(s)`. In the future, this function will be deprecated in favor of
42/// `parse_borrowed`.
43pub fn parse(
44    s: &str,
45) -> Result<Vec<format_description::BorrowedFormatItem<'_>>, error::InvalidFormatDescription> {
46    parse_borrowed::<1>(s)
47}
48
49/// Parse a sequence of items from the format description.
50///
51/// The syntax for the format description can be found in [the
52/// book](https://time-rs.github.io/book/api/format-description.html). The version of the format
53/// description is provided as the const parameter. **It is recommended to use version 2.**
54pub fn parse_borrowed<const VERSION: usize>(
55    s: &str,
56) -> Result<Vec<format_description::BorrowedFormatItem<'_>>, error::InvalidFormatDescription> {
57    validate_version!(VERSION);
58    let mut lexed = lexer::lex::<VERSION>(s.as_bytes());
59    let ast = ast::parse::<_, VERSION>(&mut lexed);
60    let format_items = format_item::parse(ast);
61    Ok(format_items
62        .map(|res| res.and_then(TryInto::try_into))
63        .collect::<Result<_, _>>()?)
64}
65
66/// Parse a sequence of items from the format description.
67///
68/// The syntax for the format description can be found in [the
69/// book](https://time-rs.github.io/book/api/format-description.html). The version of the format
70/// description is provided as the const parameter.
71///
72/// Unlike [`parse`], this function returns [`OwnedFormatItem`], which owns its contents. This means
73/// that there is no lifetime that needs to be handled. **It is recommended to use version 2.**
74///
75/// [`OwnedFormatItem`]: crate::format_description::OwnedFormatItem
76pub fn parse_owned<const VERSION: usize>(
77    s: &str,
78) -> Result<format_description::OwnedFormatItem, error::InvalidFormatDescription> {
79    validate_version!(VERSION);
80    let mut lexed = lexer::lex::<VERSION>(s.as_bytes());
81    let ast = ast::parse::<_, VERSION>(&mut lexed);
82    let format_items = format_item::parse(ast);
83    let items = format_items.collect::<Result<Box<_>, _>>()?;
84    Ok(items.into())
85}
86
87/// A location within a string.
88#[derive(Clone, Copy)]
89struct Location {
90    /// The zero-indexed byte of the string.
91    byte: u32,
92}
93
94impl Location {
95    /// Create a new [`Span`] from `self` to `other`.
96    const fn to(self, end: Self) -> Span {
97        Span { start: self, end }
98    }
99
100    /// Offset the location by the provided amount.
101    ///
102    /// Note that this assumes the resulting location is on the same line as the original location.
103    #[must_use = "this does not modify the original value"]
104    const fn offset(&self, offset: u32) -> Self {
105        Self {
106            byte: self.byte + offset,
107        }
108    }
109
110    /// Create an error with the provided message at this location.
111    const fn error(self, message: &'static str) -> ErrorInner {
112        ErrorInner {
113            _message: message,
114            _span: Span {
115                start: self,
116                end: self,
117            },
118        }
119    }
120}
121
122/// A start and end point within a string.
123#[derive(Clone, Copy)]
124struct Span {
125    #[allow(clippy::missing_docs_in_private_items)]
126    start: Location,
127    #[allow(clippy::missing_docs_in_private_items)]
128    end: Location,
129}
130
131impl Span {
132    /// Obtain a `Span` pointing at the start of the pre-existing span.
133    #[must_use = "this does not modify the original value"]
134    const fn shrink_to_start(&self) -> Self {
135        Self {
136            start: self.start,
137            end: self.start,
138        }
139    }
140
141    /// Obtain a `Span` pointing at the end of the pre-existing span.
142    #[must_use = "this does not modify the original value"]
143    const fn shrink_to_end(&self) -> Self {
144        Self {
145            start: self.end,
146            end: self.end,
147        }
148    }
149
150    /// Obtain a `Span` that ends before the provided position of the pre-existing span.
151    #[must_use = "this does not modify the original value"]
152    const fn shrink_to_before(&self, pos: u32) -> Self {
153        Self {
154            start: self.start,
155            end: Location {
156                byte: self.start.byte + pos - 1,
157            },
158        }
159    }
160
161    /// Obtain a `Span` that starts after provided position to the end of the pre-existing span.
162    #[must_use = "this does not modify the original value"]
163    const fn shrink_to_after(&self, pos: u32) -> Self {
164        Self {
165            start: Location {
166                byte: self.start.byte + pos + 1,
167            },
168            end: self.end,
169        }
170    }
171
172    /// Create an error with the provided message at this span.
173    const fn error(self, message: &'static str) -> ErrorInner {
174        ErrorInner {
175            _message: message,
176            _span: self,
177        }
178    }
179}
180
181/// A value with an associated [`Span`].
182#[derive(Clone, Copy)]
183struct Spanned<T> {
184    /// The value.
185    value: T,
186    /// Where the value was in the format string.
187    span: Span,
188}
189
190impl<T> core::ops::Deref for Spanned<T> {
191    type Target = T;
192
193    fn deref(&self) -> &Self::Target {
194        &self.value
195    }
196}
197
198/// Helper trait to attach a [`Span`] to a value.
199trait SpannedValue: Sized {
200    /// Attach a [`Span`] to a value.
201    fn spanned(self, span: Span) -> Spanned<Self>;
202}
203
204impl<T> SpannedValue for T {
205    fn spanned(self, span: Span) -> Spanned<Self> {
206        Spanned { value: self, span }
207    }
208}
209
210/// The internal error type.
211struct ErrorInner {
212    /// The message displayed to the user.
213    _message: &'static str,
214    /// Where the error originated.
215    _span: Span,
216}
217
218/// A complete error description.
219struct Error {
220    /// The internal error.
221    _inner: Unused<ErrorInner>,
222    /// The error needed for interoperability with the rest of `time`.
223    public: error::InvalidFormatDescription,
224}
225
226impl From<Error> for error::InvalidFormatDescription {
227    fn from(error: Error) -> Self {
228        error.public
229    }
230}
231
232/// A value that may be used in the future, but currently is not.
233///
234/// This struct exists so that data can semantically be passed around without _actually_ passing it
235/// around. This way the data still exists if it is needed in the future.
236// `PhantomData` is not used directly because we don't want to introduce any trait implementations.
237struct Unused<T>(core::marker::PhantomData<T>);
238
239/// Indicate that a value is currently unused.
240fn unused<T>(_: T) -> Unused<T> {
241    Unused(core::marker::PhantomData)
242}