| 1 | use std::num::NonZeroU16; | 
| 2 | use std::str::{self, FromStr}; | 
|---|
| 3 |  | 
|---|
| 4 | use super::{ast, unused, Error, Span, Spanned, Unused}; | 
|---|
| 5 |  | 
|---|
| 6 | pub(super) fn parse<'a>( | 
|---|
| 7 | ast_items: impl Iterator<Item = Result<ast::Item<'a>, Error>>, | 
|---|
| 8 | ) -> impl Iterator<Item = Result<Item<'a>, Error>> { | 
|---|
| 9 | ast_items.map(|ast_item: Result , Error>| ast_item.and_then(op:Item::from_ast)) | 
|---|
| 10 | } | 
|---|
| 11 |  | 
|---|
| 12 | pub(super) enum Item<'a> { | 
|---|
| 13 | Literal(&'a [u8]), | 
|---|
| 14 | Component(Component), | 
|---|
| 15 | Optional { | 
|---|
| 16 | value: Box<[Self]>, | 
|---|
| 17 | _span: Unused<Span>, | 
|---|
| 18 | }, | 
|---|
| 19 | First { | 
|---|
| 20 | value: Box<[Box<[Self]>]>, | 
|---|
| 21 | _span: Unused<Span>, | 
|---|
| 22 | }, | 
|---|
| 23 | } | 
|---|
| 24 |  | 
|---|
| 25 | impl Item<'_> { | 
|---|
| 26 | pub(super) fn from_ast(ast_item: ast::Item<'_>) -> Result<Item<'_>, Error> { | 
|---|
| 27 | Ok(match ast_item { | 
|---|
| 28 | ast::Item::Component { | 
|---|
| 29 | _opening_bracket: _, | 
|---|
| 30 | _leading_whitespace: _, | 
|---|
| 31 | name, | 
|---|
| 32 | modifiers, | 
|---|
| 33 | _trailing_whitespace: _, | 
|---|
| 34 | _closing_bracket: _, | 
|---|
| 35 | } => Item::Component(component_from_ast(&name, &modifiers)?), | 
|---|
| 36 | ast::Item::Literal(Spanned { value, span: _ }) => Item::Literal(value), | 
|---|
| 37 | ast::Item::EscapedBracket { | 
|---|
| 38 | _first: _, | 
|---|
| 39 | _second: _, | 
|---|
| 40 | } => Item::Literal( b"["), | 
|---|
| 41 | ast::Item::Optional { | 
|---|
| 42 | opening_bracket, | 
|---|
| 43 | _leading_whitespace: _, | 
|---|
| 44 | _optional_kw: _, | 
|---|
| 45 | _whitespace: _, | 
|---|
| 46 | nested_format_description, | 
|---|
| 47 | closing_bracket, | 
|---|
| 48 | } => { | 
|---|
| 49 | let items = nested_format_description | 
|---|
| 50 | .items | 
|---|
| 51 | .into_vec() | 
|---|
| 52 | .into_iter() | 
|---|
| 53 | .map(Item::from_ast) | 
|---|
| 54 | .collect::<Result<_, _>>()?; | 
|---|
| 55 | Item::Optional { | 
|---|
| 56 | value: items, | 
|---|
| 57 | _span: unused(opening_bracket.to(closing_bracket)), | 
|---|
| 58 | } | 
|---|
| 59 | } | 
|---|
| 60 | ast::Item::First { | 
|---|
| 61 | opening_bracket, | 
|---|
| 62 | _leading_whitespace: _, | 
|---|
| 63 | _first_kw: _, | 
|---|
| 64 | _whitespace: _, | 
|---|
| 65 | nested_format_descriptions, | 
|---|
| 66 | closing_bracket, | 
|---|
| 67 | } => { | 
|---|
| 68 | let items = nested_format_descriptions | 
|---|
| 69 | .into_vec() | 
|---|
| 70 | .into_iter() | 
|---|
| 71 | .map(|nested_format_description| { | 
|---|
| 72 | nested_format_description | 
|---|
| 73 | .items | 
|---|
| 74 | .into_vec() | 
|---|
| 75 | .into_iter() | 
|---|
| 76 | .map(Item::from_ast) | 
|---|
| 77 | .collect() | 
|---|
| 78 | }) | 
|---|
| 79 | .collect::<Result<_, _>>()?; | 
|---|
| 80 | Item::First { | 
|---|
| 81 | value: items, | 
|---|
| 82 | _span: unused(opening_bracket.to(closing_bracket)), | 
|---|
| 83 | } | 
|---|
| 84 | } | 
|---|
| 85 | }) | 
|---|
| 86 | } | 
|---|
| 87 | } | 
|---|
| 88 |  | 
|---|
| 89 | impl From<Item<'_>> for crate::format_description::public::OwnedFormatItem { | 
|---|
| 90 | fn from(item: Item<'_>) -> Self { | 
|---|
| 91 | match item { | 
|---|
| 92 | Item::Literal(literal: &[u8]) => Self::Literal(literal.to_vec().into_boxed_slice()), | 
|---|
| 93 | Item::Component(component: Component) => Self::Component(component.into()), | 
|---|
| 94 | Item::Optional { value: Box<[Item<'_>]>, _span: _ } => Self::Optional(Box::new(value.into())), | 
|---|
| 95 | Item::First { value: Box<[Box<[Item<'_>]>]>, _span: _ } => { | 
|---|
| 96 | Self::First(value.into_vec().into_iter().map(Into::into).collect()) | 
|---|
| 97 | } | 
|---|
| 98 | } | 
|---|
| 99 | } | 
|---|
| 100 | } | 
|---|
| 101 |  | 
|---|
| 102 | impl<'a> From<Box<[Item<'a>]>> for crate::format_description::public::OwnedFormatItem { | 
|---|
| 103 | fn from(items: Box<[Item<'a>]>) -> Self { | 
|---|
| 104 | let items: Vec >= items.into_vec(); | 
|---|
| 105 | match <[_; 1]>::try_from(items) { | 
|---|
| 106 | Ok([item: Item<'a>]) => item.into(), | 
|---|
| 107 | Err(vec: Vec >) => Self::Compound(vec.into_iter().map(Into::into).collect()), | 
|---|
| 108 | } | 
|---|
| 109 | } | 
|---|
| 110 | } | 
|---|
| 111 |  | 
|---|
| 112 | macro_rules! component_definition { | 
|---|
| 113 | (@if_required required then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($then)* }; | 
|---|
| 114 | (@if_required then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($($else)*)? }; | 
|---|
| 115 | (@if_from_str from_str then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($then)* }; | 
|---|
| 116 | (@if_from_str then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($($else)*)? }; | 
|---|
| 117 |  | 
|---|
| 118 | ($vis:vis enum $name:ident { | 
|---|
| 119 | $($variant:ident = $parse_variant:literal {$( | 
|---|
| 120 | $(#[$required:tt])? | 
|---|
| 121 | $field:ident = $parse_field:literal: | 
|---|
| 122 | Option<$(#[$from_str:tt])? $field_type:ty> | 
|---|
| 123 | => $target_field:ident | 
|---|
| 124 | ),* $(,)?}),* $(,)? | 
|---|
| 125 | }) => { | 
|---|
| 126 | $vis enum $name { | 
|---|
| 127 | $($variant($variant),)* | 
|---|
| 128 | } | 
|---|
| 129 |  | 
|---|
| 130 | $($vis struct $variant { | 
|---|
| 131 | $($field: Option<$field_type>),* | 
|---|
| 132 | })* | 
|---|
| 133 |  | 
|---|
| 134 | $(impl $variant { | 
|---|
| 135 | fn with_modifiers( | 
|---|
| 136 | modifiers: &[ast::Modifier<'_>], | 
|---|
| 137 | _component_span: Span, | 
|---|
| 138 | ) -> Result<Self, Error> | 
|---|
| 139 | { | 
|---|
| 140 | #[allow(unused_mut)] | 
|---|
| 141 | let mut this = Self { | 
|---|
| 142 | $($field: None),* | 
|---|
| 143 | }; | 
|---|
| 144 |  | 
|---|
| 145 | for modifier in modifiers { | 
|---|
| 146 | $(#[allow(clippy::string_lit_as_bytes)] | 
|---|
| 147 | if modifier.key.eq_ignore_ascii_case($parse_field.as_bytes()) { | 
|---|
| 148 | this.$field = component_definition!(@if_from_str $($from_str)? | 
|---|
| 149 | then { | 
|---|
| 150 | parse_from_modifier_value::<$field_type>(&modifier.value)? | 
|---|
| 151 | } else { | 
|---|
| 152 | <$field_type>::from_modifier_value(&modifier.value)? | 
|---|
| 153 | }); | 
|---|
| 154 | continue; | 
|---|
| 155 | })* | 
|---|
| 156 | return Err(modifier.key.span.error( "invalid modifier key")); | 
|---|
| 157 | } | 
|---|
| 158 |  | 
|---|
| 159 | $(component_definition! { @if_required $($required)? then { | 
|---|
| 160 | if this.$field.is_none() { | 
|---|
| 161 | return Err(_component_span.error( "missing required modifier")); | 
|---|
| 162 | } | 
|---|
| 163 | }})* | 
|---|
| 164 |  | 
|---|
| 165 | Ok(this) | 
|---|
| 166 | } | 
|---|
| 167 | })* | 
|---|
| 168 |  | 
|---|
| 169 | impl From<$name> for crate::format_description::public::Component { | 
|---|
| 170 | fn from(component: $name) -> Self { | 
|---|
| 171 | match component {$( | 
|---|
| 172 | $name::$variant($variant { $($field),* }) => { | 
|---|
| 173 | $crate::format_description::public::Component::$variant( | 
|---|
| 174 | super::public::modifier::$variant {$( | 
|---|
| 175 | $target_field: component_definition! { @if_required $($required)? | 
|---|
| 176 | then { | 
|---|
| 177 | match $field { | 
|---|
| 178 | Some(value) => value.into(), | 
|---|
| 179 | None => bug!( "required modifier was not set"), | 
|---|
| 180 | } | 
|---|
| 181 | } else { | 
|---|
| 182 | $field.unwrap_or_default().into() | 
|---|
| 183 | } | 
|---|
| 184 | } | 
|---|
| 185 | ),*} | 
|---|
| 186 | ) | 
|---|
| 187 | } | 
|---|
| 188 | )*} | 
|---|
| 189 | } | 
|---|
| 190 | } | 
|---|
| 191 |  | 
|---|
| 192 | fn component_from_ast( | 
|---|
| 193 | name: &Spanned<&[u8]>, | 
|---|
| 194 | modifiers: &[ast::Modifier<'_>], | 
|---|
| 195 | ) -> Result<Component, Error> { | 
|---|
| 196 | $(#[allow(clippy::string_lit_as_bytes)] | 
|---|
| 197 | if name.eq_ignore_ascii_case($parse_variant.as_bytes()) { | 
|---|
| 198 | return Ok(Component::$variant($variant::with_modifiers(&modifiers, name.span)?)); | 
|---|
| 199 | })* | 
|---|
| 200 | Err(name.span.error( "invalid component")) | 
|---|
| 201 | } | 
|---|
| 202 | } | 
|---|
| 203 | } | 
|---|
| 204 |  | 
|---|
| 205 | component_definition! { | 
|---|
| 206 | pub(super) enum Component { | 
|---|
| 207 | Day = "day"{ | 
|---|
| 208 | padding = "padding": Option<Padding> => padding, | 
|---|
| 209 | }, | 
|---|
| 210 | End = "end"{}, | 
|---|
| 211 | Hour = "hour"{ | 
|---|
| 212 | padding = "padding": Option<Padding> => padding, | 
|---|
| 213 | base = "repr": Option<HourBase> => is_12_hour_clock, | 
|---|
| 214 | }, | 
|---|
| 215 | Ignore = "ignore"{ | 
|---|
| 216 | #[required] | 
|---|
| 217 | count = "count": Option<#[from_str] NonZeroU16> => count, | 
|---|
| 218 | }, | 
|---|
| 219 | Minute = "minute"{ | 
|---|
| 220 | padding = "padding": Option<Padding> => padding, | 
|---|
| 221 | }, | 
|---|
| 222 | Month = "month"{ | 
|---|
| 223 | padding = "padding": Option<Padding> => padding, | 
|---|
| 224 | repr = "repr": Option<MonthRepr> => repr, | 
|---|
| 225 | case_sensitive = "case_sensitive": Option<MonthCaseSensitive> => case_sensitive, | 
|---|
| 226 | }, | 
|---|
| 227 | OffsetHour = "offset_hour"{ | 
|---|
| 228 | sign_behavior = "sign": Option<SignBehavior> => sign_is_mandatory, | 
|---|
| 229 | padding = "padding": Option<Padding> => padding, | 
|---|
| 230 | }, | 
|---|
| 231 | OffsetMinute = "offset_minute"{ | 
|---|
| 232 | padding = "padding": Option<Padding> => padding, | 
|---|
| 233 | }, | 
|---|
| 234 | OffsetSecond = "offset_second"{ | 
|---|
| 235 | padding = "padding": Option<Padding> => padding, | 
|---|
| 236 | }, | 
|---|
| 237 | Ordinal = "ordinal"{ | 
|---|
| 238 | padding = "padding": Option<Padding> => padding, | 
|---|
| 239 | }, | 
|---|
| 240 | Period = "period"{ | 
|---|
| 241 | case = "case": Option<PeriodCase> => is_uppercase, | 
|---|
| 242 | case_sensitive = "case_sensitive": Option<PeriodCaseSensitive> => case_sensitive, | 
|---|
| 243 | }, | 
|---|
| 244 | Second = "second"{ | 
|---|
| 245 | padding = "padding": Option<Padding> => padding, | 
|---|
| 246 | }, | 
|---|
| 247 | Subsecond = "subsecond"{ | 
|---|
| 248 | digits = "digits": Option<SubsecondDigits> => digits, | 
|---|
| 249 | }, | 
|---|
| 250 | UnixTimestamp = "unix_timestamp"{ | 
|---|
| 251 | precision = "precision": Option<UnixTimestampPrecision> => precision, | 
|---|
| 252 | sign_behavior = "sign": Option<SignBehavior> => sign_is_mandatory, | 
|---|
| 253 | }, | 
|---|
| 254 | Weekday = "weekday"{ | 
|---|
| 255 | repr = "repr": Option<WeekdayRepr> => repr, | 
|---|
| 256 | one_indexed = "one_indexed": Option<WeekdayOneIndexed> => one_indexed, | 
|---|
| 257 | case_sensitive = "case_sensitive": Option<WeekdayCaseSensitive> => case_sensitive, | 
|---|
| 258 | }, | 
|---|
| 259 | WeekNumber = "week_number"{ | 
|---|
| 260 | padding = "padding": Option<Padding> => padding, | 
|---|
| 261 | repr = "repr": Option<WeekNumberRepr> => repr, | 
|---|
| 262 | }, | 
|---|
| 263 | Year = "year"{ | 
|---|
| 264 | padding = "padding": Option<Padding> => padding, | 
|---|
| 265 | repr = "repr": Option<YearRepr> => repr, | 
|---|
| 266 | base = "base": Option<YearBase> => iso_week_based, | 
|---|
| 267 | sign_behavior = "sign": Option<SignBehavior> => sign_is_mandatory, | 
|---|
| 268 | }, | 
|---|
| 269 | } | 
|---|
| 270 | } | 
|---|
| 271 |  | 
|---|
| 272 | macro_rules! target_ty { | 
|---|
| 273 | ($name:ident $type:ty) => { | 
|---|
| 274 | $type | 
|---|
| 275 | }; | 
|---|
| 276 | ($name:ident) => { | 
|---|
| 277 | super::public::modifier::$name | 
|---|
| 278 | }; | 
|---|
| 279 | } | 
|---|
| 280 |  | 
|---|
| 281 | /// Get the target value for a given enum. | 
|---|
| 282 | macro_rules! target_value { | 
|---|
| 283 | ($name:ident $variant:ident $value:expr) => { | 
|---|
| 284 | $value | 
|---|
| 285 | }; | 
|---|
| 286 | ($name:ident $variant:ident) => { | 
|---|
| 287 | super::public::modifier::$name::$variant | 
|---|
| 288 | }; | 
|---|
| 289 | } | 
|---|
| 290 |  | 
|---|
| 291 | macro_rules! modifier { | 
|---|
| 292 | ($( | 
|---|
| 293 | enum $name:ident $(($target_ty:ty))? { | 
|---|
| 294 | $( | 
|---|
| 295 | $(#[$attr:meta])? | 
|---|
| 296 | $variant:ident $(($target_value:expr))? = $parse_variant:literal | 
|---|
| 297 | ),* $(,)? | 
|---|
| 298 | } | 
|---|
| 299 | )+) => {$( | 
|---|
| 300 | #[derive(Default)] | 
|---|
| 301 | enum $name { | 
|---|
| 302 | $($(#[$attr])? $variant),* | 
|---|
| 303 | } | 
|---|
| 304 |  | 
|---|
| 305 | impl $name { | 
|---|
| 306 | /// Parse the modifier from its string representation. | 
|---|
| 307 | fn from_modifier_value(value: &Spanned<&[u8]>) -> Result<Option<Self>, Error> { | 
|---|
| 308 | $(if value.eq_ignore_ascii_case($parse_variant) { | 
|---|
| 309 | return Ok(Some(Self::$variant)); | 
|---|
| 310 | })* | 
|---|
| 311 | Err(value.span.error( "invalid modifier value")) | 
|---|
| 312 | } | 
|---|
| 313 | } | 
|---|
| 314 |  | 
|---|
| 315 | impl From<$name> for target_ty!($name $($target_ty)?) { | 
|---|
| 316 | fn from(modifier: $name) -> Self { | 
|---|
| 317 | match modifier { | 
|---|
| 318 | $($name::$variant => target_value!($name $variant $($target_value)?)),* | 
|---|
| 319 | } | 
|---|
| 320 | } | 
|---|
| 321 | } | 
|---|
| 322 | )+}; | 
|---|
| 323 | } | 
|---|
| 324 |  | 
|---|
| 325 | modifier! { | 
|---|
| 326 | enum HourBase(bool) { | 
|---|
| 327 | Twelve(true) = b"12", | 
|---|
| 328 | #[default] | 
|---|
| 329 | TwentyFour(false) = b"24", | 
|---|
| 330 | } | 
|---|
| 331 |  | 
|---|
| 332 | enum MonthCaseSensitive(bool) { | 
|---|
| 333 | False(false) = b"false", | 
|---|
| 334 | #[default] | 
|---|
| 335 | True(true) = b"true", | 
|---|
| 336 | } | 
|---|
| 337 |  | 
|---|
| 338 | enum MonthRepr { | 
|---|
| 339 | #[default] | 
|---|
| 340 | Numerical = b"numerical", | 
|---|
| 341 | Long = b"long", | 
|---|
| 342 | Short = b"short", | 
|---|
| 343 | } | 
|---|
| 344 |  | 
|---|
| 345 | enum Padding { | 
|---|
| 346 | Space = b"space", | 
|---|
| 347 | #[default] | 
|---|
| 348 | Zero = b"zero", | 
|---|
| 349 | None = b"none", | 
|---|
| 350 | } | 
|---|
| 351 |  | 
|---|
| 352 | enum PeriodCase(bool) { | 
|---|
| 353 | Lower(false) = b"lower", | 
|---|
| 354 | #[default] | 
|---|
| 355 | Upper(true) = b"upper", | 
|---|
| 356 | } | 
|---|
| 357 |  | 
|---|
| 358 | enum PeriodCaseSensitive(bool) { | 
|---|
| 359 | False(false) = b"false", | 
|---|
| 360 | #[default] | 
|---|
| 361 | True(true) = b"true", | 
|---|
| 362 | } | 
|---|
| 363 |  | 
|---|
| 364 | enum SignBehavior(bool) { | 
|---|
| 365 | #[default] | 
|---|
| 366 | Automatic(false) = b"automatic", | 
|---|
| 367 | Mandatory(true) = b"mandatory", | 
|---|
| 368 | } | 
|---|
| 369 |  | 
|---|
| 370 | enum SubsecondDigits { | 
|---|
| 371 | One = b"1", | 
|---|
| 372 | Two = b"2", | 
|---|
| 373 | Three = b"3", | 
|---|
| 374 | Four = b"4", | 
|---|
| 375 | Five = b"5", | 
|---|
| 376 | Six = b"6", | 
|---|
| 377 | Seven = b"7", | 
|---|
| 378 | Eight = b"8", | 
|---|
| 379 | Nine = b"9", | 
|---|
| 380 | #[default] | 
|---|
| 381 | OneOrMore = b"1+", | 
|---|
| 382 | } | 
|---|
| 383 |  | 
|---|
| 384 | enum UnixTimestampPrecision { | 
|---|
| 385 | #[default] | 
|---|
| 386 | Second = b"second", | 
|---|
| 387 | Millisecond = b"millisecond", | 
|---|
| 388 | Microsecond = b"microsecond", | 
|---|
| 389 | Nanosecond = b"nanosecond", | 
|---|
| 390 | } | 
|---|
| 391 |  | 
|---|
| 392 | enum WeekNumberRepr { | 
|---|
| 393 | #[default] | 
|---|
| 394 | Iso = b"iso", | 
|---|
| 395 | Sunday = b"sunday", | 
|---|
| 396 | Monday = b"monday", | 
|---|
| 397 | } | 
|---|
| 398 |  | 
|---|
| 399 | enum WeekdayCaseSensitive(bool) { | 
|---|
| 400 | False(false) = b"false", | 
|---|
| 401 | #[default] | 
|---|
| 402 | True(true) = b"true", | 
|---|
| 403 | } | 
|---|
| 404 |  | 
|---|
| 405 | enum WeekdayOneIndexed(bool) { | 
|---|
| 406 | False(false) = b"false", | 
|---|
| 407 | #[default] | 
|---|
| 408 | True(true) = b"true", | 
|---|
| 409 | } | 
|---|
| 410 |  | 
|---|
| 411 | enum WeekdayRepr { | 
|---|
| 412 | Short = b"short", | 
|---|
| 413 | #[default] | 
|---|
| 414 | Long = b"long", | 
|---|
| 415 | Sunday = b"sunday", | 
|---|
| 416 | Monday = b"monday", | 
|---|
| 417 | } | 
|---|
| 418 |  | 
|---|
| 419 | enum YearBase(bool) { | 
|---|
| 420 | #[default] | 
|---|
| 421 | Calendar(false) = b"calendar", | 
|---|
| 422 | IsoWeek(true) = b"iso_week", | 
|---|
| 423 | } | 
|---|
| 424 |  | 
|---|
| 425 | enum YearRepr { | 
|---|
| 426 | #[default] | 
|---|
| 427 | Full = b"full", | 
|---|
| 428 | Century = b"century", | 
|---|
| 429 | LastTwo = b"last_two", | 
|---|
| 430 | } | 
|---|
| 431 | } | 
|---|
| 432 |  | 
|---|
| 433 | fn parse_from_modifier_value<T: FromStr>(value: &Spanned<&[u8]>) -> Result<Option<T>, Error> { | 
|---|
| 434 | str::from_utf8(value) | 
|---|
| 435 | .ok() | 
|---|
| 436 | .and_then(|val| val.parse::<T>().ok()) | 
|---|
| 437 | .map(|val| Some(val)) | 
|---|
| 438 | .ok_or_else(|| value.span.error(message: "invalid modifier value")) | 
|---|
| 439 | } | 
|---|
| 440 |  | 
|---|