time/format_description/parse/
format_item.rs

1//! Typed, validated representation of a parsed format description.
2
3use alloc::boxed::Box;
4use alloc::string::String;
5use core::num::NonZeroU16;
6use core::str::{self, FromStr};
7
8use super::{ast, unused, Error, Span, Spanned};
9use crate::internal_macros::bug;
10
11/// Parse an AST iterator into a sequence of format items.
12pub(super) fn parse<'a>(
13    ast_items: impl Iterator<Item = Result<ast::Item<'a>, Error>>,
14) -> impl Iterator<Item = Result<Item<'a>, Error>> {
15    ast_items.map(|ast_item| ast_item.and_then(Item::from_ast))
16}
17
18/// A description of how to format and parse one part of a type.
19pub(super) enum Item<'a> {
20    /// A literal string.
21    Literal(&'a [u8]),
22    /// Part of a type, along with its modifiers.
23    Component(Component),
24    /// A sequence of optional items.
25    Optional {
26        /// The items themselves.
27        value: Box<[Self]>,
28        /// The span of the full sequence.
29        span: Span,
30    },
31    /// The first matching parse of a sequence of format descriptions.
32    First {
33        /// The sequence of format descriptions.
34        value: Box<[Box<[Self]>]>,
35        /// The span of the full sequence.
36        span: Span,
37    },
38}
39
40impl Item<'_> {
41    /// Parse an AST item into a format item.
42    pub(super) fn from_ast(ast_item: ast::Item<'_>) -> Result<Item<'_>, Error> {
43        Ok(match ast_item {
44            ast::Item::Component {
45                _opening_bracket: _,
46                _leading_whitespace: _,
47                name,
48                modifiers,
49                _trailing_whitespace: _,
50                _closing_bracket: _,
51            } => Item::Component(component_from_ast(&name, &modifiers)?),
52            ast::Item::Literal(Spanned { value, span: _ }) => Item::Literal(value),
53            ast::Item::EscapedBracket {
54                _first: _,
55                _second: _,
56            } => Item::Literal(b"["),
57            ast::Item::Optional {
58                opening_bracket,
59                _leading_whitespace: _,
60                _optional_kw: _,
61                _whitespace: _,
62                nested_format_description,
63                closing_bracket,
64            } => {
65                let items = nested_format_description
66                    .items
67                    .into_vec()
68                    .into_iter()
69                    .map(Item::from_ast)
70                    .collect::<Result<_, _>>()?;
71                Item::Optional {
72                    value: items,
73                    span: opening_bracket.to(closing_bracket),
74                }
75            }
76            ast::Item::First {
77                opening_bracket,
78                _leading_whitespace: _,
79                _first_kw: _,
80                _whitespace: _,
81                nested_format_descriptions,
82                closing_bracket,
83            } => {
84                let items = nested_format_descriptions
85                    .into_vec()
86                    .into_iter()
87                    .map(|nested_format_description| {
88                        nested_format_description
89                            .items
90                            .into_vec()
91                            .into_iter()
92                            .map(Item::from_ast)
93                            .collect()
94                    })
95                    .collect::<Result<_, _>>()?;
96                Item::First {
97                    value: items,
98                    span: opening_bracket.to(closing_bracket),
99                }
100            }
101        })
102    }
103}
104
105impl<'a> TryFrom<Item<'a>> for crate::format_description::BorrowedFormatItem<'a> {
106    type Error = Error;
107
108    fn try_from(item: Item<'a>) -> Result<Self, Self::Error> {
109        match item {
110            Item::Literal(literal) => Ok(Self::Literal(literal)),
111            Item::Component(component) => Ok(Self::Component(component.into())),
112            Item::Optional { value: _, span } => Err(Error {
113                _inner: unused(span.error(
114                    "optional items are not supported in runtime-parsed format descriptions",
115                )),
116                public: crate::error::InvalidFormatDescription::NotSupported {
117                    what: "optional item",
118                    context: "runtime-parsed format descriptions",
119                    index: span.start.byte as _,
120                },
121            }),
122            Item::First { value: _, span } => Err(Error {
123                _inner: unused(span.error(
124                    "'first' items are not supported in runtime-parsed format descriptions",
125                )),
126                public: crate::error::InvalidFormatDescription::NotSupported {
127                    what: "'first' item",
128                    context: "runtime-parsed format descriptions",
129                    index: span.start.byte as _,
130                },
131            }),
132        }
133    }
134}
135
136impl From<Item<'_>> for crate::format_description::OwnedFormatItem {
137    fn from(item: Item<'_>) -> Self {
138        match item {
139            Item::Literal(literal) => Self::Literal(literal.to_vec().into_boxed_slice()),
140            Item::Component(component) => Self::Component(component.into()),
141            Item::Optional { value, span: _ } => Self::Optional(Box::new(value.into())),
142            Item::First { value, span: _ } => {
143                Self::First(value.into_vec().into_iter().map(Into::into).collect())
144            }
145        }
146    }
147}
148
149impl<'a> From<Box<[Item<'a>]>> for crate::format_description::OwnedFormatItem {
150    fn from(items: Box<[Item<'a>]>) -> Self {
151        let items = items.into_vec();
152        match <[_; 1]>::try_from(items) {
153            Ok([item]) => item.into(),
154            Err(vec) => Self::Compound(vec.into_iter().map(Into::into).collect()),
155        }
156    }
157}
158
159/// Declare the `Component` struct.
160macro_rules! component_definition {
161    (@if_required required then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($then)* };
162    (@if_required then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($($else)*)? };
163    (@if_from_str from_str then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($then)* };
164    (@if_from_str then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($($else)*)? };
165
166    ($vis:vis enum $name:ident {
167        $($variant:ident = $parse_variant:literal {$(
168            $(#[$required:tt])?
169            $field:ident = $parse_field:literal:
170            Option<$(#[$from_str:tt])? $field_type:ty>
171            => $target_field:ident
172        ),* $(,)?}),* $(,)?
173    }) => {
174        $vis enum $name {
175            $($variant($variant),)*
176        }
177
178        $($vis struct $variant {
179            $($field: Option<$field_type>),*
180        })*
181
182        $(impl $variant {
183            /// Parse the component from the AST, given its modifiers.
184            fn with_modifiers(
185                modifiers: &[ast::Modifier<'_>],
186                _component_span: Span,
187            ) -> Result<Self, Error>
188            {
189                // rustc will complain if the modifier is empty.
190                #[allow(unused_mut)]
191                let mut this = Self {
192                    $($field: None),*
193                };
194
195                for modifier in modifiers {
196                    $(#[allow(clippy::string_lit_as_bytes)]
197                    if modifier.key.eq_ignore_ascii_case($parse_field.as_bytes()) {
198                        this.$field = component_definition!(@if_from_str $($from_str)?
199                            then {
200                                parse_from_modifier_value::<$field_type>(&modifier.value)?
201                            } else {
202                                <$field_type>::from_modifier_value(&modifier.value)?
203                            });
204                        continue;
205                    })*
206                    return Err(Error {
207                        _inner: unused(modifier.key.span.error("invalid modifier key")),
208                        public: crate::error::InvalidFormatDescription::InvalidModifier {
209                            value: String::from_utf8_lossy(*modifier.key).into_owned(),
210                            index: modifier.key.span.start.byte as _,
211                        }
212                    });
213                }
214
215                $(component_definition! { @if_required $($required)? then {
216                    if this.$field.is_none() {
217                        return Err(Error {
218                            _inner: unused(_component_span.error("missing required modifier")),
219                            public:
220                                crate::error::InvalidFormatDescription::MissingRequiredModifier {
221                                    name: $parse_field,
222                                    index: _component_span.start.byte as _,
223                                }
224                        });
225                    }
226                }})*
227
228                Ok(this)
229            }
230        })*
231
232        impl From<$name> for crate::format_description::Component {
233            fn from(component: $name) -> Self {
234                match component {$(
235                    $name::$variant($variant { $($field),* }) => {
236                        $crate::format_description::component::Component::$variant(
237                            $crate::format_description::modifier::$variant {$(
238                                $target_field: component_definition! { @if_required $($required)?
239                                    then {
240                                        match $field {
241                                            Some(value) => value.into(),
242                                            None => bug!("required modifier was not set"),
243                                        }
244                                    } else {
245                                        $field.unwrap_or_default().into()
246                                    }
247                                }
248                            ),*}
249                        )
250                    }
251                )*}
252            }
253        }
254
255        /// Parse a component from the AST, given its name and modifiers.
256        fn component_from_ast(
257            name: &Spanned<&[u8]>,
258            modifiers: &[ast::Modifier<'_>],
259        ) -> Result<Component, Error> {
260            $(#[allow(clippy::string_lit_as_bytes)]
261            if name.eq_ignore_ascii_case($parse_variant.as_bytes()) {
262                return Ok(Component::$variant($variant::with_modifiers(&modifiers, name.span)?));
263            })*
264            Err(Error {
265                _inner: unused(name.span.error("invalid component")),
266                public: crate::error::InvalidFormatDescription::InvalidComponentName {
267                    name: String::from_utf8_lossy(name).into_owned(),
268                    index: name.span.start.byte as _,
269                },
270            })
271        }
272    }
273}
274
275// Keep in alphabetical order.
276component_definition! {
277    pub(super) enum Component {
278        Day = "day" {
279            padding = "padding": Option<Padding> => padding,
280        },
281        End = "end" {},
282        Hour = "hour" {
283            padding = "padding": Option<Padding> => padding,
284            base = "repr": Option<HourBase> => is_12_hour_clock,
285        },
286        Ignore = "ignore" {
287            #[required]
288            count = "count": Option<#[from_str] NonZeroU16> => count,
289        },
290        Minute = "minute" {
291            padding = "padding": Option<Padding> => padding,
292        },
293        Month = "month" {
294            padding = "padding": Option<Padding> => padding,
295            repr = "repr": Option<MonthRepr> => repr,
296            case_sensitive = "case_sensitive": Option<MonthCaseSensitive> => case_sensitive,
297        },
298        OffsetHour = "offset_hour" {
299            sign_behavior = "sign": Option<SignBehavior> => sign_is_mandatory,
300            padding = "padding": Option<Padding> => padding,
301        },
302        OffsetMinute = "offset_minute" {
303            padding = "padding": Option<Padding> => padding,
304        },
305        OffsetSecond = "offset_second" {
306            padding = "padding": Option<Padding> => padding,
307        },
308        Ordinal = "ordinal" {
309            padding = "padding": Option<Padding> => padding,
310        },
311        Period = "period" {
312            case = "case": Option<PeriodCase> => is_uppercase,
313            case_sensitive = "case_sensitive": Option<PeriodCaseSensitive> => case_sensitive,
314        },
315        Second = "second" {
316            padding = "padding": Option<Padding> => padding,
317        },
318        Subsecond = "subsecond" {
319            digits = "digits": Option<SubsecondDigits> => digits,
320        },
321        UnixTimestamp = "unix_timestamp" {
322            precision = "precision": Option<UnixTimestampPrecision> => precision,
323            sign_behavior = "sign": Option<SignBehavior> => sign_is_mandatory,
324        },
325        Weekday = "weekday" {
326            repr = "repr": Option<WeekdayRepr> => repr,
327            one_indexed = "one_indexed": Option<WeekdayOneIndexed> => one_indexed,
328            case_sensitive = "case_sensitive": Option<WeekdayCaseSensitive> => case_sensitive,
329        },
330        WeekNumber = "week_number" {
331            padding = "padding": Option<Padding> => padding,
332            repr = "repr": Option<WeekNumberRepr> => repr,
333        },
334        Year = "year" {
335            padding = "padding": Option<Padding> => padding,
336            repr = "repr": Option<YearRepr> => repr,
337            range = "range": Option<YearRange> => range,
338            base = "base": Option<YearBase> => iso_week_based,
339            sign_behavior = "sign": Option<SignBehavior> => sign_is_mandatory,
340        },
341    }
342}
343
344/// Get the target type for a given enum.
345macro_rules! target_ty {
346    ($name:ident $type:ty) => {
347        $type
348    };
349    ($name:ident) => {
350        $crate::format_description::modifier::$name
351    };
352}
353
354/// Get the target value for a given enum.
355macro_rules! target_value {
356    ($name:ident $variant:ident $value:expr) => {
357        $value
358    };
359    ($name:ident $variant:ident) => {
360        $crate::format_description::modifier::$name::$variant
361    };
362}
363
364/// Declare the various modifiers.
365///
366/// For the general case, ordinary syntax can be used. Note that you _must_ declare a default
367/// variant. The only significant change is that the string representation of the variant must be
368/// provided after the variant name. For example, `Numerical = b"numerical"` declares a variant
369/// named `Numerical` with the string representation `b"numerical"`. This is the value that will be
370/// used when parsing the modifier. The value is not case sensitive.
371///
372/// If the type in the public API does not have the same name as the type in the internal
373/// representation, then the former must be specified in parenthesis after the internal name. For
374/// example, `HourBase(bool)` has an internal name "HourBase", but is represented as a boolean in
375/// the public API.
376///
377/// By default, the internal variant name is assumed to be the same as the public variant name. If
378/// this is not the case, the qualified path to the variant must be specified in parenthesis after
379/// the internal variant name. For example, `Twelve(true)` has an internal variant name "Twelve",
380/// but is represented as `true` in the public API.
381macro_rules! modifier {
382    ($(
383        enum $name:ident $(($target_ty:ty))? {
384            $(
385                $(#[$attr:meta])?
386                $variant:ident $(($target_value:expr))? = $parse_variant:literal
387            ),* $(,)?
388        }
389    )+) => {$(
390        #[derive(Default)]
391        enum $name {
392            $($(#[$attr])? $variant),*
393        }
394
395        impl $name {
396            /// Parse the modifier from its string representation.
397            fn from_modifier_value(value: &Spanned<&[u8]>) -> Result<Option<Self>, Error> {
398                $(if value.eq_ignore_ascii_case($parse_variant) {
399                    return Ok(Some(Self::$variant));
400                })*
401                Err(Error {
402                    _inner: unused(value.span.error("invalid modifier value")),
403                    public: crate::error::InvalidFormatDescription::InvalidModifier {
404                        value: String::from_utf8_lossy(value).into_owned(),
405                        index: value.span.start.byte as _,
406                    },
407                })
408            }
409        }
410
411        impl From<$name> for target_ty!($name $($target_ty)?) {
412            fn from(modifier: $name) -> Self {
413                match modifier {
414                    $($name::$variant => target_value!($name $variant $($target_value)?)),*
415                }
416            }
417        }
418    )+};
419}
420
421// Keep in alphabetical order.
422modifier! {
423    enum HourBase(bool) {
424        Twelve(true) = b"12",
425        #[default]
426        TwentyFour(false) = b"24",
427    }
428
429    enum MonthCaseSensitive(bool) {
430        False(false) = b"false",
431        #[default]
432        True(true) = b"true",
433    }
434
435    enum MonthRepr {
436        #[default]
437        Numerical = b"numerical",
438        Long = b"long",
439        Short = b"short",
440    }
441
442    enum Padding {
443        Space = b"space",
444        #[default]
445        Zero = b"zero",
446        None = b"none",
447    }
448
449    enum PeriodCase(bool) {
450        Lower(false) = b"lower",
451        #[default]
452        Upper(true) = b"upper",
453    }
454
455    enum PeriodCaseSensitive(bool) {
456        False(false) = b"false",
457        #[default]
458        True(true) = b"true",
459    }
460
461    enum SignBehavior(bool) {
462        #[default]
463        Automatic(false) = b"automatic",
464        Mandatory(true) = b"mandatory",
465    }
466
467    enum SubsecondDigits {
468        One = b"1",
469        Two = b"2",
470        Three = b"3",
471        Four = b"4",
472        Five = b"5",
473        Six = b"6",
474        Seven = b"7",
475        Eight = b"8",
476        Nine = b"9",
477        #[default]
478        OneOrMore = b"1+",
479    }
480
481    enum UnixTimestampPrecision {
482        #[default]
483        Second = b"second",
484        Millisecond = b"millisecond",
485        Microsecond = b"microsecond",
486        Nanosecond = b"nanosecond",
487    }
488
489    enum WeekNumberRepr {
490        #[default]
491        Iso = b"iso",
492        Sunday = b"sunday",
493        Monday = b"monday",
494    }
495
496    enum WeekdayCaseSensitive(bool) {
497        False(false) = b"false",
498        #[default]
499        True(true) = b"true",
500    }
501
502    enum WeekdayOneIndexed(bool) {
503        False(false) = b"false",
504        #[default]
505        True(true) = b"true",
506    }
507
508    enum WeekdayRepr {
509        Short = b"short",
510        #[default]
511        Long = b"long",
512        Sunday = b"sunday",
513        Monday = b"monday",
514    }
515
516    enum YearBase(bool) {
517        #[default]
518        Calendar(false) = b"calendar",
519        IsoWeek(true) = b"iso_week",
520    }
521
522    enum YearRepr {
523        #[default]
524        Full = b"full",
525        Century = b"century",
526        LastTwo = b"last_two",
527    }
528
529    enum YearRange {
530        Standard = b"standard",
531        #[default]
532        Extended = b"extended",
533    }
534}
535
536/// Parse a modifier value using `FromStr`. Requires the modifier value to be valid UTF-8.
537fn parse_from_modifier_value<T: FromStr>(value: &Spanned<&[u8]>) -> Result<Option<T>, Error> {
538    str::from_utf8(value)
539        .ok()
540        .and_then(|val| val.parse::<T>().ok())
541        .map(|val| Some(val))
542        .ok_or_else(|| Error {
543            _inner: unused(value.span.error("invalid modifier value")),
544            public: crate::error::InvalidFormatDescription::InvalidModifier {
545                value: String::from_utf8_lossy(value).into_owned(),
546                index: value.span.start.byte as _,
547            },
548        })
549}