1 | //! Parser for format descriptions. |
2 | |
3 | use alloc::boxed::Box; |
4 | use alloc::vec::Vec; |
5 | |
6 | /// A helper macro to make version restrictions simpler to read and write. |
7 | macro_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). |
14 | macro_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 | |
21 | mod ast; |
22 | mod format_item; |
23 | mod lexer; |
24 | |
25 | /// A struct that is used to ensure that the version is valid. |
26 | struct Version<const N: usize>; |
27 | impl<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`. |
41 | pub 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.** |
53 | pub 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 |
76 | pub 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)] |
91 | struct Location { |
92 | /// The zero-indexed byte of the string. |
93 | byte: u32, |
94 | } |
95 | |
96 | impl 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)] |
126 | struct 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 | |
133 | impl 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)] |
185 | struct Spanned<T> { |
186 | /// The value. |
187 | value: T, |
188 | /// Where the value was in the format string. |
189 | span: Span, |
190 | } |
191 | |
192 | impl<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. |
201 | trait SpannedValue: Sized { |
202 | /// Attach a [`Span`] to a value. |
203 | fn spanned(self, span: Span) -> Spanned<Self>; |
204 | } |
205 | |
206 | impl<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. |
213 | struct 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. |
221 | struct 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 | |
228 | impl 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. |
239 | struct Unused<T>(core::marker::PhantomData<T>); |
240 | |
241 | /// Indicate that a value is currently unused. |
242 | #[allow (clippy::missing_const_for_fn)] // false positive |
243 | fn unused<T>(_: T) -> Unused<T> { |
244 | Unused(core::marker::PhantomData) |
245 | } |
246 | |