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