| 1 | //! Parser for format descriptions. |
| 2 | |
| 3 | use alloc::boxed::Box; |
| 4 | use alloc::vec::Vec; |
| 5 | |
| 6 | pub use self::strftime::{parse_strftime_borrowed, parse_strftime_owned}; |
| 7 | use crate::{error, format_description}; |
| 8 | |
| 9 | /// A helper macro to make version restrictions simpler to read and write. |
| 10 | macro_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). |
| 17 | macro_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 | |
| 24 | mod ast; |
| 25 | mod format_item; |
| 26 | mod lexer; |
| 27 | mod strftime; |
| 28 | |
| 29 | /// A struct that is used to ensure that the version is valid. |
| 30 | struct Version<const N: usize>; |
| 31 | impl<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`. |
| 45 | pub 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.** |
| 56 | pub 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 |
| 78 | pub 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. |
| 90 | fn 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)] |
| 104 | struct Location { |
| 105 | /// The zero-indexed byte of the string. |
| 106 | byte: u32, |
| 107 | } |
| 108 | |
| 109 | impl 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)] |
| 147 | struct 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 | |
| 154 | impl 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)] |
| 206 | struct Spanned<T> { |
| 207 | /// The value. |
| 208 | value: T, |
| 209 | /// Where the value was in the format string. |
| 210 | span: Span, |
| 211 | } |
| 212 | |
| 213 | impl<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. |
| 222 | trait SpannedValue: Sized { |
| 223 | /// Attach a [`Span`] to a value. |
| 224 | fn spanned(self, span: Span) -> Spanned<Self>; |
| 225 | } |
| 226 | |
| 227 | impl<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. |
| 234 | struct 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. |
| 242 | struct 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 | |
| 249 | impl 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. |
| 260 | struct Unused<T>(core::marker::PhantomData<T>); |
| 261 | |
| 262 | /// Indicate that a value is currently unused. |
| 263 | fn unused<T>(_: T) -> Unused<T> { |
| 264 | Unused(core::marker::PhantomData) |
| 265 | } |
| 266 | |