1use std::num::NonZero;
2use std::str::{self, FromStr};
3
4use super::{Error, Span, Spanned, Unused, ast, unused};
5
6pub(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| ast_item.and_then(Item::from_ast))
10}
11
12pub(super) enum Item<'a> {
13 Literal(&'a [u8]),
14 StringLiteral(&'a str),
15 Component(Component),
16 Optional {
17 value: Box<[Self]>,
18 _span: Unused<Span>,
19 },
20 First {
21 value: Box<[Box<[Self]>]>,
22 _span: Unused<Span>,
23 },
24}
25
26impl Item<'_> {
27 pub(super) fn from_ast(ast_item: ast::Item<'_>) -> Result<Item<'_>, Error> {
28 Ok(match ast_item {
29 ast::Item::Component {
30 _opening_bracket: _,
31 _leading_whitespace: _,
32 name,
33 modifiers,
34 _trailing_whitespace: _,
35 _closing_bracket: _,
36 } => Item::Component(component_from_ast(&name, &modifiers)?),
37 ast::Item::Literal(Spanned { value, span: _ }) => {
38 if let Ok(value) = str::from_utf8(value) {
39 Item::StringLiteral(value)
40 } else {
41 Item::Literal(value)
42 }
43 }
44 ast::Item::EscapedBracket {
45 _first: _,
46 _second: _,
47 } => Item::StringLiteral("["),
48 ast::Item::Optional {
49 opening_bracket,
50 _leading_whitespace: _,
51 _optional_kw: _,
52 _whitespace: _,
53 nested_format_description,
54 closing_bracket,
55 } => {
56 let items = nested_format_description
57 .items
58 .into_vec()
59 .into_iter()
60 .map(Item::from_ast)
61 .collect::<Result<_, _>>()?;
62 Item::Optional {
63 value: items,
64 _span: unused(opening_bracket.to(closing_bracket)),
65 }
66 }
67 ast::Item::First {
68 opening_bracket,
69 _leading_whitespace: _,
70 _first_kw: _,
71 _whitespace: _,
72 nested_format_descriptions,
73 closing_bracket,
74 } => {
75 let items = nested_format_descriptions
76 .into_vec()
77 .into_iter()
78 .map(|nested_format_description| {
79 nested_format_description
80 .items
81 .into_vec()
82 .into_iter()
83 .map(Item::from_ast)
84 .collect()
85 })
86 .collect::<Result<_, _>>()?;
87 Item::First {
88 value: items,
89 _span: unused(opening_bracket.to(closing_bracket)),
90 }
91 }
92 })
93 }
94}
95
96impl From<Item<'_>> for crate::format_description::public::OwnedFormatItem {
97 fn from(item: Item<'_>) -> Self {
98 match item {
99 Item::Literal(literal) => Self::Literal(literal.to_vec().into_boxed_slice()),
100 Item::StringLiteral(string) => Self::StringLiteral(string.to_owned().into_boxed_str()),
101 Item::Component(component) => Self::Component(component.into()),
102 Item::Optional { value, _span: _ } => Self::Optional(Box::new(value.into())),
103 Item::First { value, _span: _ } => {
104 Self::First(value.into_vec().into_iter().map(Into::into).collect())
105 }
106 }
107 }
108}
109
110impl<'a> From<Box<[Item<'a>]>> for crate::format_description::public::OwnedFormatItem {
111 fn from(items: Box<[Item<'a>]>) -> Self {
112 let items = items.into_vec();
113 match <[_; 1]>::try_from(items) {
114 Ok([item]) => item.into(),
115 Err(vec) => Self::Compound(vec.into_iter().map(Into::into).collect()),
116 }
117 }
118}
119
120macro_rules! component_definition {
121 (@if_required required then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($then)* };
122 (@if_required then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($($else)*)? };
123 (@if_from_str from_str then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($then)* };
124 (@if_from_str then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($($else)*)? };
125
126 ($vis:vis enum $name:ident {
127 $($variant:ident = $parse_variant:literal {$(
128 $(#[$required:tt])?
129 $field:ident = $parse_field:literal:
130 Option<$(#[$from_str:tt])? $field_type:ty>
131 ),* $(,)?}),* $(,)?
132 }) => {
133 $vis enum $name {
134 $($variant($variant),)*
135 }
136
137 $($vis struct $variant {
138 $($field: Option<$field_type>),*
139 })*
140
141 $(impl $variant {
142 fn with_modifiers(
143 modifiers: &[ast::Modifier<'_>],
144 _component_span: Span,
145 ) -> Result<Self, Error>
146 {
147 #[allow(unused_mut)]
148 let mut this = Self {
149 $($field: None),*
150 };
151
152 for modifier in modifiers {
153 $(#[allow(clippy::string_lit_as_bytes)]
154 if modifier.key.eq_ignore_ascii_case($parse_field.as_bytes()) {
155 this.$field = component_definition!(@if_from_str $($from_str)?
156 then {
157 parse_from_modifier_value::<$field_type>(&modifier.value)?
158 } else {
159 <$field_type>::from_modifier_value(&modifier.value)?
160 });
161 continue;
162 })*
163 return Err(modifier.key.span.error("invalid modifier key"));
164 }
165
166 $(component_definition! { @if_required $($required)? then {
167 if this.$field.is_none() {
168 return Err(_component_span.error("missing required modifier"));
169 }
170 }})*
171
172 Ok(this)
173 }
174 })*
175
176 fn component_from_ast(
177 name: &Spanned<&[u8]>,
178 modifiers: &[ast::Modifier<'_>],
179 ) -> Result<Component, Error> {
180 $(#[allow(clippy::string_lit_as_bytes)]
181 if name.eq_ignore_ascii_case($parse_variant.as_bytes()) {
182 return Ok(Component::$variant($variant::with_modifiers(&modifiers, name.span)?));
183 })*
184 Err(name.span.error("invalid component"))
185 }
186 }
187}
188
189component_definition! {
190 pub(super) enum Component {
191 Day = "day" {
192 padding = "padding": Option<Padding>,
193 },
194 End = "end" {
195 trailing_input = "trailing_input": Option<TrailingInput>,
196 },
197 Hour = "hour" {
198 padding = "padding": Option<Padding>,
199 base = "repr": Option<HourBase>,
200 },
201 Ignore = "ignore" {
202 #[required]
203 count = "count": Option<#[from_str] NonZero<u16>>,
204 },
205 Minute = "minute" {
206 padding = "padding": Option<Padding>,
207 },
208 Month = "month" {
209 padding = "padding": Option<Padding>,
210 repr = "repr": Option<MonthRepr>,
211 case_sensitive = "case_sensitive": Option<MonthCaseSensitive>,
212 },
213 OffsetHour = "offset_hour" {
214 sign_behavior = "sign": Option<SignBehavior>,
215 padding = "padding": Option<Padding>,
216 },
217 OffsetMinute = "offset_minute" {
218 padding = "padding": Option<Padding>,
219 },
220 OffsetSecond = "offset_second" {
221 padding = "padding": Option<Padding>,
222 },
223 Ordinal = "ordinal" {
224 padding = "padding": Option<Padding>,
225 },
226 Period = "period" {
227 case = "case": Option<PeriodCase>,
228 case_sensitive = "case_sensitive": Option<PeriodCaseSensitive>,
229 },
230 Second = "second" {
231 padding = "padding": Option<Padding>,
232 },
233 Subsecond = "subsecond" {
234 digits = "digits": Option<SubsecondDigits>,
235 },
236 UnixTimestamp = "unix_timestamp" {
237 precision = "precision": Option<UnixTimestampPrecision>,
238 sign_behavior = "sign": Option<SignBehavior>,
239 },
240 Weekday = "weekday" {
241 repr = "repr": Option<WeekdayRepr>,
242 one_indexed = "one_indexed": Option<WeekdayOneIndexed>,
243 case_sensitive = "case_sensitive": Option<WeekdayCaseSensitive>,
244 },
245 WeekNumber = "week_number" {
246 padding = "padding": Option<Padding>,
247 repr = "repr": Option<WeekNumberRepr>,
248 },
249 Year = "year" {
250 padding = "padding": Option<Padding>,
251 repr = "repr": Option<YearRepr>,
252 range = "range": Option<YearRange>,
253 base = "base": Option<YearBase>,
254 sign_behavior = "sign": Option<SignBehavior>,
255 },
256 }
257}
258
259impl From<Component> for crate::format_description::public::Component {
260 #[inline]
261 fn from(component: Component) -> Self {
262 use crate::format_description::public::modifier;
263 match component {
264 Component::Day(Day { padding }) => Self::Day(modifier::Day {
265 padding: padding.unwrap_or_default().into(),
266 }),
267 Component::End(End { trailing_input }) => Self::End(modifier::End {
268 trailing_input: trailing_input.unwrap_or_default().into(),
269 }),
270 Component::Hour(Hour { padding, base }) => match base.unwrap_or_default() {
271 HourBase::Twelve => Self::Hour12(modifier::Hour12 {
272 padding: padding.unwrap_or_default().into(),
273 }),
274 HourBase::TwentyFour => Self::Hour24(modifier::Hour24 {
275 padding: padding.unwrap_or_default().into(),
276 }),
277 },
278 Component::Ignore(Ignore { count }) => Self::Ignore(modifier::Ignore {
279 count: match count {
280 Some(value) => value,
281 None => bug!("required modifier was not set"),
282 },
283 }),
284 Component::Minute(Minute { padding }) => Self::Minute(modifier::Minute {
285 padding: padding.unwrap_or_default().into(),
286 }),
287 Component::Month(Month {
288 padding,
289 repr,
290 case_sensitive,
291 }) => match repr.unwrap_or_default() {
292 MonthRepr::Numerical => Self::MonthNumerical(modifier::MonthNumerical {
293 padding: padding.unwrap_or_default().into(),
294 }),
295 MonthRepr::Long => Self::MonthLong(modifier::MonthLong {
296 case_sensitive: case_sensitive.unwrap_or_default().into(),
297 }),
298 MonthRepr::Short => Self::MonthShort(modifier::MonthShort {
299 case_sensitive: case_sensitive.unwrap_or_default().into(),
300 }),
301 },
302 Component::OffsetHour(OffsetHour {
303 sign_behavior,
304 padding,
305 }) => Self::OffsetHour(modifier::OffsetHour {
306 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
307 padding: padding.unwrap_or_default().into(),
308 }),
309 Component::OffsetMinute(OffsetMinute { padding }) => {
310 Self::OffsetMinute(modifier::OffsetMinute {
311 padding: padding.unwrap_or_default().into(),
312 })
313 }
314 Component::OffsetSecond(OffsetSecond { padding }) => {
315 Self::OffsetSecond(modifier::OffsetSecond {
316 padding: padding.unwrap_or_default().into(),
317 })
318 }
319 Component::Ordinal(Ordinal { padding }) => Self::Ordinal(modifier::Ordinal {
320 padding: padding.unwrap_or_default().into(),
321 }),
322 Component::Period(Period {
323 case,
324 case_sensitive,
325 }) => Self::Period(modifier::Period {
326 is_uppercase: case.unwrap_or_default().into(),
327 case_sensitive: case_sensitive.unwrap_or_default().into(),
328 }),
329 Component::Second(Second { padding }) => Self::Second(modifier::Second {
330 padding: padding.unwrap_or_default().into(),
331 }),
332 Component::Subsecond(Subsecond { digits }) => Self::Subsecond(modifier::Subsecond {
333 digits: digits.unwrap_or_default().into(),
334 }),
335 Component::UnixTimestamp(UnixTimestamp {
336 precision,
337 sign_behavior,
338 }) => match precision.unwrap_or_default() {
339 UnixTimestampPrecision::Second => {
340 Self::UnixTimestampSecond(modifier::UnixTimestampSecond {
341 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
342 })
343 }
344 UnixTimestampPrecision::Millisecond => {
345 Self::UnixTimestampMillisecond(modifier::UnixTimestampMillisecond {
346 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
347 })
348 }
349 UnixTimestampPrecision::Microsecond => {
350 Self::UnixTimestampMicrosecond(modifier::UnixTimestampMicrosecond {
351 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
352 })
353 }
354 UnixTimestampPrecision::Nanosecond => {
355 Self::UnixTimestampNanosecond(modifier::UnixTimestampNanosecond {
356 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
357 })
358 }
359 },
360 Component::Weekday(Weekday {
361 repr,
362 one_indexed,
363 case_sensitive,
364 }) => match repr.unwrap_or_default() {
365 WeekdayRepr::Short => Self::WeekdayShort(modifier::WeekdayShort {
366 case_sensitive: case_sensitive.unwrap_or_default().into(),
367 }),
368 WeekdayRepr::Long => Self::WeekdayLong(modifier::WeekdayLong {
369 case_sensitive: case_sensitive.unwrap_or_default().into(),
370 }),
371 WeekdayRepr::Sunday => Self::WeekdaySunday(modifier::WeekdaySunday {
372 one_indexed: one_indexed.unwrap_or_default().into(),
373 }),
374 WeekdayRepr::Monday => Self::WeekdayMonday(modifier::WeekdayMonday {
375 one_indexed: one_indexed.unwrap_or_default().into(),
376 }),
377 },
378 Component::WeekNumber(WeekNumber { padding, repr }) => match repr.unwrap_or_default() {
379 WeekNumberRepr::Iso => Self::WeekNumberIso(modifier::WeekNumberIso {
380 padding: padding.unwrap_or_default().into(),
381 }),
382 WeekNumberRepr::Sunday => Self::WeekNumberSunday(modifier::WeekNumberSunday {
383 padding: padding.unwrap_or_default().into(),
384 }),
385 WeekNumberRepr::Monday => Self::WeekNumberMonday(modifier::WeekNumberMonday {
386 padding: padding.unwrap_or_default().into(),
387 }),
388 },
389 Component::Year(Year {
390 padding,
391 repr,
392 range,
393 base,
394 sign_behavior,
395 }) => match (
396 base.unwrap_or_default(),
397 repr.unwrap_or_default(),
398 range.unwrap_or_default(),
399 ) {
400 (YearBase::Calendar, YearRepr::Full, YearRange::Extended) => {
401 Self::CalendarYearFullExtendedRange(modifier::CalendarYearFullExtendedRange {
402 padding: padding.unwrap_or_default().into(),
403 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
404 })
405 }
406 (YearBase::Calendar, YearRepr::Full, _) => {
407 Self::CalendarYearFullStandardRange(modifier::CalendarYearFullStandardRange {
408 padding: padding.unwrap_or_default().into(),
409 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
410 })
411 }
412 (YearBase::Calendar, YearRepr::Century, YearRange::Extended) => {
413 Self::CalendarYearCenturyExtendedRange(
414 modifier::CalendarYearCenturyExtendedRange {
415 padding: padding.unwrap_or_default().into(),
416 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
417 },
418 )
419 }
420 (YearBase::Calendar, YearRepr::Century, _) => {
421 Self::CalendarYearCenturyStandardRange(
422 modifier::CalendarYearCenturyStandardRange {
423 padding: padding.unwrap_or_default().into(),
424 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
425 },
426 )
427 }
428 (YearBase::IsoWeek, YearRepr::Full, YearRange::Extended) => {
429 Self::IsoYearFullExtendedRange(modifier::IsoYearFullExtendedRange {
430 padding: padding.unwrap_or_default().into(),
431 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
432 })
433 }
434 (YearBase::IsoWeek, YearRepr::Full, _) => {
435 Self::IsoYearFullStandardRange(modifier::IsoYearFullStandardRange {
436 padding: padding.unwrap_or_default().into(),
437 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
438 })
439 }
440 (YearBase::IsoWeek, YearRepr::Century, YearRange::Extended) => {
441 Self::IsoYearCenturyExtendedRange(modifier::IsoYearCenturyExtendedRange {
442 padding: padding.unwrap_or_default().into(),
443 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
444 })
445 }
446 (YearBase::IsoWeek, YearRepr::Century, _) => {
447 Self::IsoYearCenturyStandardRange(modifier::IsoYearCenturyStandardRange {
448 padding: padding.unwrap_or_default().into(),
449 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
450 })
451 }
452 (YearBase::Calendar, YearRepr::LastTwo, _) => {
453 Self::CalendarYearLastTwo(modifier::CalendarYearLastTwo {
454 padding: padding.unwrap_or_default().into(),
455 })
456 }
457 (YearBase::IsoWeek, YearRepr::LastTwo, _) => {
458 Self::IsoYearLastTwo(modifier::IsoYearLastTwo {
459 padding: padding.unwrap_or_default().into(),
460 })
461 }
462 },
463 }
464 }
465}
466
467macro_rules! target_ty {
468 ($name:ident $type:ty) => {
469 $type
470 };
471 ($name:ident) => {
472 super::public::modifier::$name
473 };
474}
475
476macro_rules! target_value {
478 ($name:ident $variant:ident $value:expr) => {
479 $value
480 };
481 ($name:ident $variant:ident) => {
482 super::public::modifier::$name::$variant
483 };
484}
485
486macro_rules! if_not_parse_only {
487 (@parse_only $($x:tt)*) => {};
488 ($($x:tt)*) => { $($x)* };
489}
490
491macro_rules! modifier {
492 ($(
493 $(@$instruction:ident)? enum $name:ident $(($target_ty:ty))? {
494 $(
495 $(#[$attr:meta])?
496 $variant:ident $(($target_value:expr))? = $parse_variant:literal
497 ),* $(,)?
498 }
499 )+) => {$(
500 #[derive(Default)]
501 enum $name {
502 $($(#[$attr])? $variant),*
503 }
504
505 impl $name {
506 fn from_modifier_value(value: &Spanned<&[u8]>) -> Result<Option<Self>, Error> {
508 $(if value.eq_ignore_ascii_case($parse_variant) {
509 return Ok(Some(Self::$variant));
510 })*
511 Err(value.span.error("invalid modifier value"))
512 }
513 }
514
515 if_not_parse_only! { $(@$instruction)?
516 impl From<$name> for target_ty!($name $($target_ty)?) {
517 fn from(modifier: $name) -> Self {
518 match modifier {
519 $($name::$variant => target_value!($name $variant $($target_value)?)),*
520 }
521 }
522 }
523 }
524 )+};
525}
526
527modifier! {
528 enum HourBase(bool) {
529 Twelve(true) = b"12",
530 #[default]
531 TwentyFour(false) = b"24",
532 }
533
534 enum MonthCaseSensitive(bool) {
535 False(false) = b"false",
536 #[default]
537 True(true) = b"true",
538 }
539
540 @parse_only enum MonthRepr {
541 #[default]
542 Numerical = b"numerical",
543 Long = b"long",
544 Short = b"short",
545 }
546
547 enum Padding {
548 Space = b"space",
549 #[default]
550 Zero = b"zero",
551 None = b"none",
552 }
553
554 enum PeriodCase(bool) {
555 Lower(false) = b"lower",
556 #[default]
557 Upper(true) = b"upper",
558 }
559
560 enum PeriodCaseSensitive(bool) {
561 False(false) = b"false",
562 #[default]
563 True(true) = b"true",
564 }
565
566 enum SignBehavior(bool) {
567 #[default]
568 Automatic(false) = b"automatic",
569 Mandatory(true) = b"mandatory",
570 }
571
572 enum SubsecondDigits {
573 One = b"1",
574 Two = b"2",
575 Three = b"3",
576 Four = b"4",
577 Five = b"5",
578 Six = b"6",
579 Seven = b"7",
580 Eight = b"8",
581 Nine = b"9",
582 #[default]
583 OneOrMore = b"1+",
584 }
585
586 enum TrailingInput {
587 #[default]
588 Prohibit = b"prohibit",
589 Discard = b"discard",
590 }
591
592 @parse_only enum UnixTimestampPrecision {
593 #[default]
594 Second = b"second",
595 Millisecond = b"millisecond",
596 Microsecond = b"microsecond",
597 Nanosecond = b"nanosecond",
598 }
599
600 @parse_only enum WeekNumberRepr {
601 #[default]
602 Iso = b"iso",
603 Sunday = b"sunday",
604 Monday = b"monday",
605 }
606
607 enum WeekdayCaseSensitive(bool) {
608 False(false) = b"false",
609 #[default]
610 True(true) = b"true",
611 }
612
613 enum WeekdayOneIndexed(bool) {
614 False(false) = b"false",
615 #[default]
616 True(true) = b"true",
617 }
618
619 @parse_only enum WeekdayRepr {
620 Short = b"short",
621 #[default]
622 Long = b"long",
623 Sunday = b"sunday",
624 Monday = b"monday",
625 }
626
627 enum YearBase(bool) {
628 #[default]
629 Calendar(false) = b"calendar",
630 IsoWeek(true) = b"iso_week",
631 }
632
633 @parse_only enum YearRepr {
634 #[default]
635 Full = b"full",
636 Century = b"century",
637 LastTwo = b"last_two",
638 }
639
640 @parse_only enum YearRange {
641 Standard = b"standard",
642 #[default]
643 Extended = b"extended",
644 }
645}
646
647fn parse_from_modifier_value<T: FromStr>(value: &Spanned<&[u8]>) -> Result<Option<T>, Error> {
648 str::from_utf8(value)
649 .ok()
650 .and_then(|val| val.parse::<T>().ok())
651 .map(|val| Some(val))
652 .ok_or_else(|| value.span.error("invalid modifier value"))
653}