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