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::NonZero;
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.
12#[inline]
13pub(super) fn parse<'a>(
14    ast_items: impl Iterator<Item = Result<ast::Item<'a>, Error>>,
15) -> impl Iterator<Item = Result<Item<'a>, Error>> {
16    ast_items.map(|ast_item| ast_item.and_then(Item::from_ast))
17}
18
19/// A description of how to format and parse one part of a type.
20pub(super) enum Item<'a> {
21    /// A literal string.
22    Literal(&'a [u8]),
23    /// Part of a type, along with its modifiers.
24    Component(Component),
25    /// A sequence of optional items.
26    Optional {
27        /// The items themselves.
28        value: Box<[Self]>,
29        /// The span of the full sequence.
30        span: Span,
31    },
32    /// The first matching parse of a sequence of format descriptions.
33    First {
34        /// The sequence of format descriptions.
35        value: Box<[Box<[Self]>]>,
36        /// The span of the full sequence.
37        span: Span,
38    },
39}
40
41impl Item<'_> {
42    /// Parse an AST item into a format item.
43    pub(super) fn from_ast(ast_item: ast::Item<'_>) -> Result<Item<'_>, Error> {
44        Ok(match ast_item {
45            ast::Item::Component {
46                _opening_bracket: _,
47                _leading_whitespace: _,
48                name,
49                modifiers,
50                _trailing_whitespace: _,
51                _closing_bracket: _,
52            } => Item::Component(component_from_ast(&name, &modifiers)?),
53            ast::Item::Literal(Spanned { value, span: _ }) => Item::Literal(value),
54            ast::Item::EscapedBracket {
55                _first: _,
56                _second: _,
57            } => Item::Literal(b"["),
58            ast::Item::Optional {
59                opening_bracket,
60                _leading_whitespace: _,
61                _optional_kw: _,
62                _whitespace: _,
63                nested_format_description,
64                closing_bracket,
65            } => {
66                let items = nested_format_description
67                    .items
68                    .into_vec()
69                    .into_iter()
70                    .map(Item::from_ast)
71                    .collect::<Result<_, _>>()?;
72                Item::Optional {
73                    value: items,
74                    span: opening_bracket.to(closing_bracket),
75                }
76            }
77            ast::Item::First {
78                opening_bracket,
79                _leading_whitespace: _,
80                _first_kw: _,
81                _whitespace: _,
82                nested_format_descriptions,
83                closing_bracket,
84            } => {
85                let items = nested_format_descriptions
86                    .into_vec()
87                    .into_iter()
88                    .map(|nested_format_description| {
89                        nested_format_description
90                            .items
91                            .into_vec()
92                            .into_iter()
93                            .map(Item::from_ast)
94                            .collect()
95                    })
96                    .collect::<Result<_, _>>()?;
97                Item::First {
98                    value: items,
99                    span: opening_bracket.to(closing_bracket),
100                }
101            }
102        })
103    }
104}
105
106impl<'a> TryFrom<Item<'a>> for crate::format_description::BorrowedFormatItem<'a> {
107    type Error = Error;
108
109    #[inline]
110    fn try_from(item: Item<'a>) -> Result<Self, Self::Error> {
111        match item {
112            Item::Literal(literal) => Ok(Self::Literal(literal)),
113            Item::Component(component) => Ok(Self::Component(component.into())),
114            Item::Optional { value: _, span } => Err(Error {
115                _inner: unused(span.error(
116                    "optional items are not supported in runtime-parsed format descriptions",
117                )),
118                public: crate::error::InvalidFormatDescription::NotSupported {
119                    what: "optional item",
120                    context: "runtime-parsed format descriptions",
121                    index: span.start.byte as usize,
122                },
123            }),
124            Item::First { value: _, span } => Err(Error {
125                _inner: unused(span.error(
126                    "'first' items are not supported in runtime-parsed format descriptions",
127                )),
128                public: crate::error::InvalidFormatDescription::NotSupported {
129                    what: "'first' item",
130                    context: "runtime-parsed format descriptions",
131                    index: span.start.byte as usize,
132                },
133            }),
134        }
135    }
136}
137
138impl From<Item<'_>> for crate::format_description::OwnedFormatItem {
139    #[inline]
140    fn from(item: Item<'_>) -> Self {
141        match item {
142            Item::Literal(literal) => Self::Literal(literal.to_vec().into_boxed_slice()),
143            Item::Component(component) => Self::Component(component.into()),
144            Item::Optional { value, span: _ } => Self::Optional(Box::new(value.into())),
145            Item::First { value, span: _ } => {
146                Self::First(value.into_vec().into_iter().map(Into::into).collect())
147            }
148        }
149    }
150}
151
152impl<'a> From<Box<[Item<'a>]>> for crate::format_description::OwnedFormatItem {
153    #[inline]
154    fn from(items: Box<[Item<'a>]>) -> Self {
155        let items = items.into_vec();
156        match <[_; 1]>::try_from(items) {
157            Ok([item]) => item.into(),
158            Err(vec) => Self::Compound(vec.into_iter().map(Into::into).collect()),
159        }
160    }
161}
162
163/// Declare the `Component` struct.
164macro_rules! component_definition {
165    (@if_required required then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($then)* };
166    (@if_required then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($($else)*)? };
167    (@if_from_str from_str then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($then)* };
168    (@if_from_str then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($($else)*)? };
169
170    ($vis:vis enum $name:ident {
171        $($variant:ident = $parse_variant:literal {$(
172            $(#[$required:tt])?
173            $field:ident = $parse_field:literal:
174            Option<$(#[$from_str:tt])? $field_type:ty>
175            => $target_field:ident
176        ),* $(,)?}),* $(,)?
177    }) => {
178        $vis enum $name {
179            $($variant($variant),)*
180        }
181
182        $($vis struct $variant {
183            $($field: Option<$field_type>),*
184        })*
185
186        $(impl $variant {
187            /// Parse the component from the AST, given its modifiers.
188            #[inline]
189            fn with_modifiers(
190                modifiers: &[ast::Modifier<'_>],
191                _component_span: Span,
192            ) -> Result<Self, Error>
193            {
194                // rustc will complain if the modifier is empty.
195                #[allow(unused_mut)]
196                let mut this = Self {
197                    $($field: None),*
198                };
199
200                for modifier in modifiers {
201                    $(#[expect(clippy::string_lit_as_bytes)]
202                    if modifier.key.eq_ignore_ascii_case($parse_field.as_bytes()) {
203                        this.$field = component_definition!(@if_from_str $($from_str)?
204                            then {
205                                parse_from_modifier_value::<$field_type>(&modifier.value)?
206                            } else {
207                                <$field_type>::from_modifier_value(&modifier.value)?
208                            });
209                        continue;
210                    })*
211                    return Err(Error {
212                        _inner: unused(modifier.key.span.error("invalid modifier key")),
213                        public: crate::error::InvalidFormatDescription::InvalidModifier {
214                            value: String::from_utf8_lossy(*modifier.key).into_owned(),
215                            index: modifier.key.span.start.byte as usize,
216                        }
217                    });
218                }
219
220                $(component_definition! { @if_required $($required)? then {
221                    if this.$field.is_none() {
222                        return Err(Error {
223                            _inner: unused(_component_span.error("missing required modifier")),
224                            public:
225                                crate::error::InvalidFormatDescription::MissingRequiredModifier {
226                                    name: $parse_field,
227                                    index: _component_span.start.byte as usize,
228                                }
229                        });
230                    }
231                }})*
232
233                Ok(this)
234            }
235        })*
236
237        impl From<$name> for crate::format_description::Component {
238            #[inline]
239            fn from(component: $name) -> Self {
240                match component {$(
241                    $name::$variant($variant { $($field),* }) => {
242                        $crate::format_description::component::Component::$variant(
243                            $crate::format_description::modifier::$variant {$(
244                                $target_field: component_definition! { @if_required $($required)?
245                                    then {
246                                        match $field {
247                                            Some(value) => value.into(),
248                                            None => bug!("required modifier was not set"),
249                                        }
250                                    } else {
251                                        $field.unwrap_or_default().into()
252                                    }
253                                }
254                            ),*}
255                        )
256                    }
257                )*}
258            }
259        }
260
261        /// Parse a component from the AST, given its name and modifiers.
262        #[inline]
263        fn component_from_ast(
264            name: &Spanned<&[u8]>,
265            modifiers: &[ast::Modifier<'_>],
266        ) -> Result<Component, Error> {
267            $(#[expect(clippy::string_lit_as_bytes)]
268            if name.eq_ignore_ascii_case($parse_variant.as_bytes()) {
269                return Ok(Component::$variant($variant::with_modifiers(&modifiers, name.span)?));
270            })*
271            Err(Error {
272                _inner: unused(name.span.error("invalid component")),
273                public: crate::error::InvalidFormatDescription::InvalidComponentName {
274                    name: String::from_utf8_lossy(name).into_owned(),
275                    index: name.span.start.byte as usize,
276                },
277            })
278        }
279    }
280}
281
282// Keep in alphabetical order.
283pub(super) enum Component {
    Day(Day),
    End(End),
    Hour(Hour),
    Ignore(Ignore),
    Minute(Minute),
    Month(Month),
    OffsetHour(OffsetHour),
    OffsetMinute(OffsetMinute),
    OffsetSecond(OffsetSecond),
    Ordinal(Ordinal),
    Period(Period),
    Second(Second),
    Subsecond(Subsecond),
    UnixTimestamp(UnixTimestamp),
    Weekday(Weekday),
    WeekNumber(WeekNumber),
    Year(Year),
}
pub(super) struct Day {
    padding: Option<Padding>,
}
pub(super) struct End {}
pub(super) struct Hour {
    padding: Option<Padding>,
    base: Option<HourBase>,
}
pub(super) struct Ignore {
    count: Option<NonZero<u16>>,
}
pub(super) struct Minute {
    padding: Option<Padding>,
}
pub(super) struct Month {
    padding: Option<Padding>,
    repr: Option<MonthRepr>,
    case_sensitive: Option<MonthCaseSensitive>,
}
pub(super) struct OffsetHour {
    sign_behavior: Option<SignBehavior>,
    padding: Option<Padding>,
}
pub(super) struct OffsetMinute {
    padding: Option<Padding>,
}
pub(super) struct OffsetSecond {
    padding: Option<Padding>,
}
pub(super) struct Ordinal {
    padding: Option<Padding>,
}
pub(super) struct Period {
    case: Option<PeriodCase>,
    case_sensitive: Option<PeriodCaseSensitive>,
}
pub(super) struct Second {
    padding: Option<Padding>,
}
pub(super) struct Subsecond {
    digits: Option<SubsecondDigits>,
}
pub(super) struct UnixTimestamp {
    precision: Option<UnixTimestampPrecision>,
    sign_behavior: Option<SignBehavior>,
}
pub(super) struct Weekday {
    repr: Option<WeekdayRepr>,
    one_indexed: Option<WeekdayOneIndexed>,
    case_sensitive: Option<WeekdayCaseSensitive>,
}
pub(super) struct WeekNumber {
    padding: Option<Padding>,
    repr: Option<WeekNumberRepr>,
}
pub(super) struct Year {
    padding: Option<Padding>,
    repr: Option<YearRepr>,
    range: Option<YearRange>,
    base: Option<YearBase>,
    sign_behavior: Option<SignBehavior>,
}
impl Day {
    /// Parse the component from the AST, given its modifiers.
    #[inline]
    fn with_modifiers(modifiers: &[ast::Modifier<'_>], _component_span: Span)
        -> Result<Self, Error> {
        #[allow(unused_mut)]
        let mut this = Self { padding: None };
        for modifier in modifiers {

            #[expect(clippy :: string_lit_as_bytes)]
            if modifier.key.eq_ignore_ascii_case("padding".as_bytes()) {
                this.padding =
                    <Padding>::from_modifier_value(&modifier.value)?;
                continue;
            }
            return Err(Error {
                        _inner: unused(modifier.key.span.error("invalid modifier key")),
                        public: crate::error::InvalidFormatDescription::InvalidModifier {
                            value: String::from_utf8_lossy(*modifier.key).into_owned(),
                            index: modifier.key.span.start.byte as usize,
                        },
                    });
        }
        Ok(this)
    }
}
impl End {
    /// Parse the component from the AST, given its modifiers.
    #[inline]
    fn with_modifiers(modifiers: &[ast::Modifier<'_>], _component_span: Span)
        -> Result<Self, Error> {
        #[allow(unused_mut)]
        let mut this = Self {};
        for modifier in modifiers {
            return Err(Error {
                        _inner: unused(modifier.key.span.error("invalid modifier key")),
                        public: crate::error::InvalidFormatDescription::InvalidModifier {
                            value: String::from_utf8_lossy(*modifier.key).into_owned(),
                            index: modifier.key.span.start.byte as usize,
                        },
                    });
        }
        Ok(this)
    }
}
impl Hour {
    /// Parse the component from the AST, given its modifiers.
    #[inline]
    fn with_modifiers(modifiers: &[ast::Modifier<'_>], _component_span: Span)
        -> Result<Self, Error> {
        #[allow(unused_mut)]
        let mut this = Self { padding: None, base: None };
        for modifier in modifiers {

            #[expect(clippy :: string_lit_as_bytes)]
            if modifier.key.eq_ignore_ascii_case("padding".as_bytes()) {
                this.padding =
                    <Padding>::from_modifier_value(&modifier.value)?;
                continue;
            }

            #[expect(clippy :: string_lit_as_bytes)]
            if modifier.key.eq_ignore_ascii_case("repr".as_bytes()) {
                this.base = <HourBase>::from_modifier_value(&modifier.value)?;
                continue;
            }
            return Err(Error {
                        _inner: unused(modifier.key.span.error("invalid modifier key")),
                        public: crate::error::InvalidFormatDescription::InvalidModifier {
                            value: String::from_utf8_lossy(*modifier.key).into_owned(),
                            index: modifier.key.span.start.byte as usize,
                        },
                    });
        }
        Ok(this)
    }
}
impl Ignore {
    /// Parse the component from the AST, given its modifiers.
    #[inline]
    fn with_modifiers(modifiers: &[ast::Modifier<'_>], _component_span: Span)
        -> Result<Self, Error> {
        #[allow(unused_mut)]
        let mut this = Self { count: None };
        for modifier in modifiers {

            #[expect(clippy :: string_lit_as_bytes)]
            if modifier.key.eq_ignore_ascii_case("count".as_bytes()) {
                this.count =
                    parse_from_modifier_value::<NonZero<u16>>(&modifier.value)?;
                continue;
            }
            return Err(Error {
                        _inner: unused(modifier.key.span.error("invalid modifier key")),
                        public: crate::error::InvalidFormatDescription::InvalidModifier {
                            value: String::from_utf8_lossy(*modifier.key).into_owned(),
                            index: modifier.key.span.start.byte as usize,
                        },
                    });
        }
        if this.count.is_none() {
            return Err(Error {
                        _inner: unused(_component_span.error("missing required modifier")),
                        public: crate::error::InvalidFormatDescription::MissingRequiredModifier {
                            name: "count",
                            index: _component_span.start.byte as usize,
                        },
                    });
        }
        Ok(this)
    }
}
impl Minute {
    /// Parse the component from the AST, given its modifiers.
    #[inline]
    fn with_modifiers(modifiers: &[ast::Modifier<'_>], _component_span: Span)
        -> Result<Self, Error> {
        #[allow(unused_mut)]
        let mut this = Self { padding: None };
        for modifier in modifiers {

            #[expect(clippy :: string_lit_as_bytes)]
            if modifier.key.eq_ignore_ascii_case("padding".as_bytes()) {
                this.padding =
                    <Padding>::from_modifier_value(&modifier.value)?;
                continue;
            }
            return Err(Error {
                        _inner: unused(modifier.key.span.error("invalid modifier key")),
                        public: crate::error::InvalidFormatDescription::InvalidModifier {
                            value: String::from_utf8_lossy(*modifier.key).into_owned(),
                            index: modifier.key.span.start.byte as usize,
                        },
                    });
        }
        Ok(this)
    }
}
impl Month {
    /// Parse the component from the AST, given its modifiers.
    #[inline]
    fn with_modifiers(modifiers: &[ast::Modifier<'_>], _component_span: Span)
        -> Result<Self, Error> {
        #[allow(unused_mut)]
        let mut this =
            Self { padding: None, repr: None, case_sensitive: None };
        for modifier in modifiers {

            #[expect(clippy :: string_lit_as_bytes)]
            if modifier.key.eq_ignore_ascii_case("padding".as_bytes()) {
                this.padding =
                    <Padding>::from_modifier_value(&modifier.value)?;
                continue;
            }

            #[expect(clippy :: string_lit_as_bytes)]
            if modifier.key.eq_ignore_ascii_case("repr".as_bytes()) {
                this.repr =
                    <MonthRepr>::from_modifier_value(&modifier.value)?;
                continue;
            }

            #[expect(clippy :: string_lit_as_bytes)]
            if modifier.key.eq_ignore_ascii_case("case_sensitive".as_bytes())
                {
                this.case_sensitive =
                    <MonthCaseSensitive>::from_modifier_value(&modifier.value)?;
                continue;
            }
            return Err(Error {
                        _inner: unused(modifier.key.span.error("invalid modifier key")),
                        public: crate::error::InvalidFormatDescription::InvalidModifier {
                            value: String::from_utf8_lossy(*modifier.key).into_owned(),
                            index: modifier.key.span.start.byte as usize,
                        },
                    });
        }
        Ok(this)
    }
}
impl OffsetHour {
    /// Parse the component from the AST, given its modifiers.
    #[inline]
    fn with_modifiers(modifiers: &[ast::Modifier<'_>], _component_span: Span)
        -> Result<Self, Error> {
        #[allow(unused_mut)]
        let mut this = Self { sign_behavior: None, padding: None };
        for modifier in modifiers {

            #[expect(clippy :: string_lit_as_bytes)]
            if modifier.key.eq_ignore_ascii_case("sign".as_bytes()) {
                this.sign_behavior =
                    <SignBehavior>::from_modifier_value(&modifier.value)?;
                continue;
            }

            #[expect(clippy :: string_lit_as_bytes)]
            if modifier.key.eq_ignore_ascii_case("padding".as_bytes()) {
                this.padding =
                    <Padding>::from_modifier_value(&modifier.value)?;
                continue;
            }
            return Err(Error {
                        _inner: unused(modifier.key.span.error("invalid modifier key")),
                        public: crate::error::InvalidFormatDescription::InvalidModifier {
                            value: String::from_utf8_lossy(*modifier.key).into_owned(),
                            index: modifier.key.span.start.byte as usize,
                        },
                    });
        }
        Ok(this)
    }
}
impl OffsetMinute {
    /// Parse the component from the AST, given its modifiers.
    #[inline]
    fn with_modifiers(modifiers: &[ast::Modifier<'_>], _component_span: Span)
        -> Result<Self, Error> {
        #[allow(unused_mut)]
        let mut this = Self { padding: None };
        for modifier in modifiers {

            #[expect(clippy :: string_lit_as_bytes)]
            if modifier.key.eq_ignore_ascii_case("padding".as_bytes()) {
                this.padding =
                    <Padding>::from_modifier_value(&modifier.value)?;
                continue;
            }
            return Err(Error {
                        _inner: unused(modifier.key.span.error("invalid modifier key")),
                        public: crate::error::InvalidFormatDescription::InvalidModifier {
                            value: String::from_utf8_lossy(*modifier.key).into_owned(),
                            index: modifier.key.span.start.byte as usize,
                        },
                    });
        }
        Ok(this)
    }
}
impl OffsetSecond {
    /// Parse the component from the AST, given its modifiers.
    #[inline]
    fn with_modifiers(modifiers: &[ast::Modifier<'_>], _component_span: Span)
        -> Result<Self, Error> {
        #[allow(unused_mut)]
        let mut this = Self { padding: None };
        for modifier in modifiers {

            #[expect(clippy :: string_lit_as_bytes)]
            if modifier.key.eq_ignore_ascii_case("padding".as_bytes()) {
                this.padding =
                    <Padding>::from_modifier_value(&modifier.value)?;
                continue;
            }
            return Err(Error {
                        _inner: unused(modifier.key.span.error("invalid modifier key")),
                        public: crate::error::InvalidFormatDescription::InvalidModifier {
                            value: String::from_utf8_lossy(*modifier.key).into_owned(),
                            index: modifier.key.span.start.byte as usize,
                        },
                    });
        }
        Ok(this)
    }
}
impl Ordinal {
    /// Parse the component from the AST, given its modifiers.
    #[inline]
    fn with_modifiers(modifiers: &[ast::Modifier<'_>], _component_span: Span)
        -> Result<Self, Error> {
        #[allow(unused_mut)]
        let mut this = Self { padding: None };
        for modifier in modifiers {

            #[expect(clippy :: string_lit_as_bytes)]
            if modifier.key.eq_ignore_ascii_case("padding".as_bytes()) {
                this.padding =
                    <Padding>::from_modifier_value(&modifier.value)?;
                continue;
            }
            return Err(Error {
                        _inner: unused(modifier.key.span.error("invalid modifier key")),
                        public: crate::error::InvalidFormatDescription::InvalidModifier {
                            value: String::from_utf8_lossy(*modifier.key).into_owned(),
                            index: modifier.key.span.start.byte as usize,
                        },
                    });
        }
        Ok(this)
    }
}
impl Period {
    /// Parse the component from the AST, given its modifiers.
    #[inline]
    fn with_modifiers(modifiers: &[ast::Modifier<'_>], _component_span: Span)
        -> Result<Self, Error> {
        #[allow(unused_mut)]
        let mut this = Self { case: None, case_sensitive: None };
        for modifier in modifiers {

            #[expect(clippy :: string_lit_as_bytes)]
            if modifier.key.eq_ignore_ascii_case("case".as_bytes()) {
                this.case =
                    <PeriodCase>::from_modifier_value(&modifier.value)?;
                continue;
            }

            #[expect(clippy :: string_lit_as_bytes)]
            if modifier.key.eq_ignore_ascii_case("case_sensitive".as_bytes())
                {
                this.case_sensitive =
                    <PeriodCaseSensitive>::from_modifier_value(&modifier.value)?;
                continue;
            }
            return Err(Error {
                        _inner: unused(modifier.key.span.error("invalid modifier key")),
                        public: crate::error::InvalidFormatDescription::InvalidModifier {
                            value: String::from_utf8_lossy(*modifier.key).into_owned(),
                            index: modifier.key.span.start.byte as usize,
                        },
                    });
        }
        Ok(this)
    }
}
impl Second {
    /// Parse the component from the AST, given its modifiers.
    #[inline]
    fn with_modifiers(modifiers: &[ast::Modifier<'_>], _component_span: Span)
        -> Result<Self, Error> {
        #[allow(unused_mut)]
        let mut this = Self { padding: None };
        for modifier in modifiers {

            #[expect(clippy :: string_lit_as_bytes)]
            if modifier.key.eq_ignore_ascii_case("padding".as_bytes()) {
                this.padding =
                    <Padding>::from_modifier_value(&modifier.value)?;
                continue;
            }
            return Err(Error {
                        _inner: unused(modifier.key.span.error("invalid modifier key")),
                        public: crate::error::InvalidFormatDescription::InvalidModifier {
                            value: String::from_utf8_lossy(*modifier.key).into_owned(),
                            index: modifier.key.span.start.byte as usize,
                        },
                    });
        }
        Ok(this)
    }
}
impl Subsecond {
    /// Parse the component from the AST, given its modifiers.
    #[inline]
    fn with_modifiers(modifiers: &[ast::Modifier<'_>], _component_span: Span)
        -> Result<Self, Error> {
        #[allow(unused_mut)]
        let mut this = Self { digits: None };
        for modifier in modifiers {

            #[expect(clippy :: string_lit_as_bytes)]
            if modifier.key.eq_ignore_ascii_case("digits".as_bytes()) {
                this.digits =
                    <SubsecondDigits>::from_modifier_value(&modifier.value)?;
                continue;
            }
            return Err(Error {
                        _inner: unused(modifier.key.span.error("invalid modifier key")),
                        public: crate::error::InvalidFormatDescription::InvalidModifier {
                            value: String::from_utf8_lossy(*modifier.key).into_owned(),
                            index: modifier.key.span.start.byte as usize,
                        },
                    });
        }
        Ok(this)
    }
}
impl UnixTimestamp {
    /// Parse the component from the AST, given its modifiers.
    #[inline]
    fn with_modifiers(modifiers: &[ast::Modifier<'_>], _component_span: Span)
        -> Result<Self, Error> {
        #[allow(unused_mut)]
        let mut this = Self { precision: None, sign_behavior: None };
        for modifier in modifiers {

            #[expect(clippy :: string_lit_as_bytes)]
            if modifier.key.eq_ignore_ascii_case("precision".as_bytes()) {
                this.precision =
                    <UnixTimestampPrecision>::from_modifier_value(&modifier.value)?;
                continue;
            }

            #[expect(clippy :: string_lit_as_bytes)]
            if modifier.key.eq_ignore_ascii_case("sign".as_bytes()) {
                this.sign_behavior =
                    <SignBehavior>::from_modifier_value(&modifier.value)?;
                continue;
            }
            return Err(Error {
                        _inner: unused(modifier.key.span.error("invalid modifier key")),
                        public: crate::error::InvalidFormatDescription::InvalidModifier {
                            value: String::from_utf8_lossy(*modifier.key).into_owned(),
                            index: modifier.key.span.start.byte as usize,
                        },
                    });
        }
        Ok(this)
    }
}
impl Weekday {
    /// Parse the component from the AST, given its modifiers.
    #[inline]
    fn with_modifiers(modifiers: &[ast::Modifier<'_>], _component_span: Span)
        -> Result<Self, Error> {
        #[allow(unused_mut)]
        let mut this =
            Self { repr: None, one_indexed: None, case_sensitive: None };
        for modifier in modifiers {

            #[expect(clippy :: string_lit_as_bytes)]
            if modifier.key.eq_ignore_ascii_case("repr".as_bytes()) {
                this.repr =
                    <WeekdayRepr>::from_modifier_value(&modifier.value)?;
                continue;
            }

            #[expect(clippy :: string_lit_as_bytes)]
            if modifier.key.eq_ignore_ascii_case("one_indexed".as_bytes()) {
                this.one_indexed =
                    <WeekdayOneIndexed>::from_modifier_value(&modifier.value)?;
                continue;
            }

            #[expect(clippy :: string_lit_as_bytes)]
            if modifier.key.eq_ignore_ascii_case("case_sensitive".as_bytes())
                {
                this.case_sensitive =
                    <WeekdayCaseSensitive>::from_modifier_value(&modifier.value)?;
                continue;
            }
            return Err(Error {
                        _inner: unused(modifier.key.span.error("invalid modifier key")),
                        public: crate::error::InvalidFormatDescription::InvalidModifier {
                            value: String::from_utf8_lossy(*modifier.key).into_owned(),
                            index: modifier.key.span.start.byte as usize,
                        },
                    });
        }
        Ok(this)
    }
}
impl WeekNumber {
    /// Parse the component from the AST, given its modifiers.
    #[inline]
    fn with_modifiers(modifiers: &[ast::Modifier<'_>], _component_span: Span)
        -> Result<Self, Error> {
        #[allow(unused_mut)]
        let mut this = Self { padding: None, repr: None };
        for modifier in modifiers {

            #[expect(clippy :: string_lit_as_bytes)]
            if modifier.key.eq_ignore_ascii_case("padding".as_bytes()) {
                this.padding =
                    <Padding>::from_modifier_value(&modifier.value)?;
                continue;
            }

            #[expect(clippy :: string_lit_as_bytes)]
            if modifier.key.eq_ignore_ascii_case("repr".as_bytes()) {
                this.repr =
                    <WeekNumberRepr>::from_modifier_value(&modifier.value)?;
                continue;
            }
            return Err(Error {
                        _inner: unused(modifier.key.span.error("invalid modifier key")),
                        public: crate::error::InvalidFormatDescription::InvalidModifier {
                            value: String::from_utf8_lossy(*modifier.key).into_owned(),
                            index: modifier.key.span.start.byte as usize,
                        },
                    });
        }
        Ok(this)
    }
}
impl Year {
    /// Parse the component from the AST, given its modifiers.
    #[inline]
    fn with_modifiers(modifiers: &[ast::Modifier<'_>], _component_span: Span)
        -> Result<Self, Error> {
        #[allow(unused_mut)]
        let mut this =
            Self {
                padding: None,
                repr: None,
                range: None,
                base: None,
                sign_behavior: None,
            };
        for modifier in modifiers {

            #[expect(clippy :: string_lit_as_bytes)]
            if modifier.key.eq_ignore_ascii_case("padding".as_bytes()) {
                this.padding =
                    <Padding>::from_modifier_value(&modifier.value)?;
                continue;
            }

            #[expect(clippy :: string_lit_as_bytes)]
            if modifier.key.eq_ignore_ascii_case("repr".as_bytes()) {
                this.repr = <YearRepr>::from_modifier_value(&modifier.value)?;
                continue;
            }

            #[expect(clippy :: string_lit_as_bytes)]
            if modifier.key.eq_ignore_ascii_case("range".as_bytes()) {
                this.range =
                    <YearRange>::from_modifier_value(&modifier.value)?;
                continue;
            }

            #[expect(clippy :: string_lit_as_bytes)]
            if modifier.key.eq_ignore_ascii_case("base".as_bytes()) {
                this.base = <YearBase>::from_modifier_value(&modifier.value)?;
                continue;
            }

            #[expect(clippy :: string_lit_as_bytes)]
            if modifier.key.eq_ignore_ascii_case("sign".as_bytes()) {
                this.sign_behavior =
                    <SignBehavior>::from_modifier_value(&modifier.value)?;
                continue;
            }
            return Err(Error {
                        _inner: unused(modifier.key.span.error("invalid modifier key")),
                        public: crate::error::InvalidFormatDescription::InvalidModifier {
                            value: String::from_utf8_lossy(*modifier.key).into_owned(),
                            index: modifier.key.span.start.byte as usize,
                        },
                    });
        }
        Ok(this)
    }
}
impl From<Component> for crate::format_description::Component {
    #[inline]
    fn from(component: Component) -> Self {
        match component {
            Component::Day(Day { padding }) => {
                crate::format_description::component::Component::Day(crate::format_description::modifier::Day {
                        padding: padding.unwrap_or_default().into(),
                    })
            }
            Component::End(End {}) => {
                crate::format_description::component::Component::End(crate::format_description::modifier::End {})
            }
            Component::Hour(Hour { padding, base }) => {
                crate::format_description::component::Component::Hour(crate::format_description::modifier::Hour {
                        padding: padding.unwrap_or_default().into(),
                        is_12_hour_clock: base.unwrap_or_default().into(),
                    })
            }
            Component::Ignore(Ignore { count }) => {
                crate::format_description::component::Component::Ignore(crate::format_description::modifier::Ignore {
                        count: match count {
                            Some(value) => value.into(),
                            None => {
                                ::core::panicking::panic_fmt(format_args!("internal error: required modifier was not set"));
                            }
                        },
                    })
            }
            Component::Minute(Minute { padding }) => {
                crate::format_description::component::Component::Minute(crate::format_description::modifier::Minute {
                        padding: padding.unwrap_or_default().into(),
                    })
            }
            Component::Month(Month { padding, repr, case_sensitive }) => {
                crate::format_description::component::Component::Month(crate::format_description::modifier::Month {
                        padding: padding.unwrap_or_default().into(),
                        repr: repr.unwrap_or_default().into(),
                        case_sensitive: case_sensitive.unwrap_or_default().into(),
                    })
            }
            Component::OffsetHour(OffsetHour { sign_behavior, padding }) => {
                crate::format_description::component::Component::OffsetHour(crate::format_description::modifier::OffsetHour {
                        sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
                        padding: padding.unwrap_or_default().into(),
                    })
            }
            Component::OffsetMinute(OffsetMinute { padding }) => {
                crate::format_description::component::Component::OffsetMinute(crate::format_description::modifier::OffsetMinute {
                        padding: padding.unwrap_or_default().into(),
                    })
            }
            Component::OffsetSecond(OffsetSecond { padding }) => {
                crate::format_description::component::Component::OffsetSecond(crate::format_description::modifier::OffsetSecond {
                        padding: padding.unwrap_or_default().into(),
                    })
            }
            Component::Ordinal(Ordinal { padding }) => {
                crate::format_description::component::Component::Ordinal(crate::format_description::modifier::Ordinal {
                        padding: padding.unwrap_or_default().into(),
                    })
            }
            Component::Period(Period { case, case_sensitive }) => {
                crate::format_description::component::Component::Period(crate::format_description::modifier::Period {
                        is_uppercase: case.unwrap_or_default().into(),
                        case_sensitive: case_sensitive.unwrap_or_default().into(),
                    })
            }
            Component::Second(Second { padding }) => {
                crate::format_description::component::Component::Second(crate::format_description::modifier::Second {
                        padding: padding.unwrap_or_default().into(),
                    })
            }
            Component::Subsecond(Subsecond { digits }) => {
                crate::format_description::component::Component::Subsecond(crate::format_description::modifier::Subsecond {
                        digits: digits.unwrap_or_default().into(),
                    })
            }
            Component::UnixTimestamp(UnixTimestamp { precision, sign_behavior
                }) => {
                crate::format_description::component::Component::UnixTimestamp(crate::format_description::modifier::UnixTimestamp {
                        precision: precision.unwrap_or_default().into(),
                        sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
                    })
            }
            Component::Weekday(Weekday { repr, one_indexed, case_sensitive })
                => {
                crate::format_description::component::Component::Weekday(crate::format_description::modifier::Weekday {
                        repr: repr.unwrap_or_default().into(),
                        one_indexed: one_indexed.unwrap_or_default().into(),
                        case_sensitive: case_sensitive.unwrap_or_default().into(),
                    })
            }
            Component::WeekNumber(WeekNumber { padding, repr }) => {
                crate::format_description::component::Component::WeekNumber(crate::format_description::modifier::WeekNumber {
                        padding: padding.unwrap_or_default().into(),
                        repr: repr.unwrap_or_default().into(),
                    })
            }
            Component::Year(Year { padding, repr, range, base, sign_behavior
                }) => {
                crate::format_description::component::Component::Year(crate::format_description::modifier::Year {
                        padding: padding.unwrap_or_default().into(),
                        repr: repr.unwrap_or_default().into(),
                        range: range.unwrap_or_default().into(),
                        iso_week_based: base.unwrap_or_default().into(),
                        sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
                    })
            }
        }
    }
}
/// Parse a component from the AST, given its name and modifiers.
#[inline]
fn component_from_ast(name: &Spanned<&[u8]>, modifiers: &[ast::Modifier<'_>])
    -> Result<Component, Error> {

    #[expect(clippy :: string_lit_as_bytes)]
    if name.eq_ignore_ascii_case("day".as_bytes()) {
        return Ok(Component::Day(Day::with_modifiers(&modifiers,
                            name.span)?));
    }

    #[expect(clippy :: string_lit_as_bytes)]
    if name.eq_ignore_ascii_case("end".as_bytes()) {
        return Ok(Component::End(End::with_modifiers(&modifiers,
                            name.span)?));
    }

    #[expect(clippy :: string_lit_as_bytes)]
    if name.eq_ignore_ascii_case("hour".as_bytes()) {
        return Ok(Component::Hour(Hour::with_modifiers(&modifiers,
                            name.span)?));
    }

    #[expect(clippy :: string_lit_as_bytes)]
    if name.eq_ignore_ascii_case("ignore".as_bytes()) {
        return Ok(Component::Ignore(Ignore::with_modifiers(&modifiers,
                            name.span)?));
    }

    #[expect(clippy :: string_lit_as_bytes)]
    if name.eq_ignore_ascii_case("minute".as_bytes()) {
        return Ok(Component::Minute(Minute::with_modifiers(&modifiers,
                            name.span)?));
    }

    #[expect(clippy :: string_lit_as_bytes)]
    if name.eq_ignore_ascii_case("month".as_bytes()) {
        return Ok(Component::Month(Month::with_modifiers(&modifiers,
                            name.span)?));
    }

    #[expect(clippy :: string_lit_as_bytes)]
    if name.eq_ignore_ascii_case("offset_hour".as_bytes()) {
        return Ok(Component::OffsetHour(OffsetHour::with_modifiers(&modifiers,
                            name.span)?));
    }

    #[expect(clippy :: string_lit_as_bytes)]
    if name.eq_ignore_ascii_case("offset_minute".as_bytes()) {
        return Ok(Component::OffsetMinute(OffsetMinute::with_modifiers(&modifiers,
                            name.span)?));
    }

    #[expect(clippy :: string_lit_as_bytes)]
    if name.eq_ignore_ascii_case("offset_second".as_bytes()) {
        return Ok(Component::OffsetSecond(OffsetSecond::with_modifiers(&modifiers,
                            name.span)?));
    }

    #[expect(clippy :: string_lit_as_bytes)]
    if name.eq_ignore_ascii_case("ordinal".as_bytes()) {
        return Ok(Component::Ordinal(Ordinal::with_modifiers(&modifiers,
                            name.span)?));
    }

    #[expect(clippy :: string_lit_as_bytes)]
    if name.eq_ignore_ascii_case("period".as_bytes()) {
        return Ok(Component::Period(Period::with_modifiers(&modifiers,
                            name.span)?));
    }

    #[expect(clippy :: string_lit_as_bytes)]
    if name.eq_ignore_ascii_case("second".as_bytes()) {
        return Ok(Component::Second(Second::with_modifiers(&modifiers,
                            name.span)?));
    }

    #[expect(clippy :: string_lit_as_bytes)]
    if name.eq_ignore_ascii_case("subsecond".as_bytes()) {
        return Ok(Component::Subsecond(Subsecond::with_modifiers(&modifiers,
                            name.span)?));
    }

    #[expect(clippy :: string_lit_as_bytes)]
    if name.eq_ignore_ascii_case("unix_timestamp".as_bytes()) {
        return Ok(Component::UnixTimestamp(UnixTimestamp::with_modifiers(&modifiers,
                            name.span)?));
    }

    #[expect(clippy :: string_lit_as_bytes)]
    if name.eq_ignore_ascii_case("weekday".as_bytes()) {
        return Ok(Component::Weekday(Weekday::with_modifiers(&modifiers,
                            name.span)?));
    }

    #[expect(clippy :: string_lit_as_bytes)]
    if name.eq_ignore_ascii_case("week_number".as_bytes()) {
        return Ok(Component::WeekNumber(WeekNumber::with_modifiers(&modifiers,
                            name.span)?));
    }

    #[expect(clippy :: string_lit_as_bytes)]
    if name.eq_ignore_ascii_case("year".as_bytes()) {
        return Ok(Component::Year(Year::with_modifiers(&modifiers,
                            name.span)?));
    }
    Err(Error {
            _inner: unused(name.span.error("invalid component")),
            public: crate::error::InvalidFormatDescription::InvalidComponentName {
                name: String::from_utf8_lossy(name).into_owned(),
                index: name.span.start.byte as usize,
            },
        })
}component_definition! {
284    pub(super) enum Component {
285        Day = "day" {
286            padding = "padding": Option<Padding> => padding,
287        },
288        End = "end" {},
289        Hour = "hour" {
290            padding = "padding": Option<Padding> => padding,
291            base = "repr": Option<HourBase> => is_12_hour_clock,
292        },
293        Ignore = "ignore" {
294            #[required]
295            count = "count": Option<#[from_str] NonZero<u16>> => count,
296        },
297        Minute = "minute" {
298            padding = "padding": Option<Padding> => padding,
299        },
300        Month = "month" {
301            padding = "padding": Option<Padding> => padding,
302            repr = "repr": Option<MonthRepr> => repr,
303            case_sensitive = "case_sensitive": Option<MonthCaseSensitive> => case_sensitive,
304        },
305        OffsetHour = "offset_hour" {
306            sign_behavior = "sign": Option<SignBehavior> => sign_is_mandatory,
307            padding = "padding": Option<Padding> => padding,
308        },
309        OffsetMinute = "offset_minute" {
310            padding = "padding": Option<Padding> => padding,
311        },
312        OffsetSecond = "offset_second" {
313            padding = "padding": Option<Padding> => padding,
314        },
315        Ordinal = "ordinal" {
316            padding = "padding": Option<Padding> => padding,
317        },
318        Period = "period" {
319            case = "case": Option<PeriodCase> => is_uppercase,
320            case_sensitive = "case_sensitive": Option<PeriodCaseSensitive> => case_sensitive,
321        },
322        Second = "second" {
323            padding = "padding": Option<Padding> => padding,
324        },
325        Subsecond = "subsecond" {
326            digits = "digits": Option<SubsecondDigits> => digits,
327        },
328        UnixTimestamp = "unix_timestamp" {
329            precision = "precision": Option<UnixTimestampPrecision> => precision,
330            sign_behavior = "sign": Option<SignBehavior> => sign_is_mandatory,
331        },
332        Weekday = "weekday" {
333            repr = "repr": Option<WeekdayRepr> => repr,
334            one_indexed = "one_indexed": Option<WeekdayOneIndexed> => one_indexed,
335            case_sensitive = "case_sensitive": Option<WeekdayCaseSensitive> => case_sensitive,
336        },
337        WeekNumber = "week_number" {
338            padding = "padding": Option<Padding> => padding,
339            repr = "repr": Option<WeekNumberRepr> => repr,
340        },
341        Year = "year" {
342            padding = "padding": Option<Padding> => padding,
343            repr = "repr": Option<YearRepr> => repr,
344            range = "range": Option<YearRange> => range,
345            base = "base": Option<YearBase> => iso_week_based,
346            sign_behavior = "sign": Option<SignBehavior> => sign_is_mandatory,
347        },
348    }
349}
350
351/// Get the target type for a given enum.
352macro_rules! target_ty {
353    ($name:ident $type:ty) => {
354        $type
355    };
356    ($name:ident) => {
357        $crate::format_description::modifier::$name
358    };
359}
360
361/// Get the target value for a given enum.
362macro_rules! target_value {
363    ($name:ident $variant:ident $value:expr) => {
364        $value
365    };
366    ($name:ident $variant:ident) => {
367        $crate::format_description::modifier::$name::$variant
368    };
369}
370
371/// Declare the various modifiers.
372///
373/// For the general case, ordinary syntax can be used. Note that you _must_ declare a default
374/// variant. The only significant change is that the string representation of the variant must be
375/// provided after the variant name. For example, `Numerical = b"numerical"` declares a variant
376/// named `Numerical` with the string representation `b"numerical"`. This is the value that will be
377/// used when parsing the modifier. The value is not case sensitive.
378///
379/// If the type in the public API does not have the same name as the type in the internal
380/// representation, then the former must be specified in parenthesis after the internal name. For
381/// example, `HourBase(bool)` has an internal name "HourBase", but is represented as a boolean in
382/// the public API.
383///
384/// By default, the internal variant name is assumed to be the same as the public variant name. If
385/// this is not the case, the qualified path to the variant must be specified in parenthesis after
386/// the internal variant name. For example, `Twelve(true)` has an internal variant name "Twelve",
387/// but is represented as `true` in the public API.
388macro_rules! modifier {
389    ($(
390        enum $name:ident $(($target_ty:ty))? {
391            $(
392                $(#[$attr:meta])?
393                $variant:ident $(($target_value:expr))? = $parse_variant:literal
394            ),* $(,)?
395        }
396    )+) => {$(
397        #[derive(Default)]
398        enum $name {
399            $($(#[$attr])? $variant),*
400        }
401
402        impl $name {
403            /// Parse the modifier from its string representation.
404            #[inline]
405            fn from_modifier_value(value: &Spanned<&[u8]>) -> Result<Option<Self>, Error> {
406                $(if value.eq_ignore_ascii_case($parse_variant) {
407                    return Ok(Some(Self::$variant));
408                })*
409                Err(Error {
410                    _inner: unused(value.span.error("invalid modifier value")),
411                    public: crate::error::InvalidFormatDescription::InvalidModifier {
412                        value: String::from_utf8_lossy(value).into_owned(),
413                        index: value.span.start.byte as usize,
414                    },
415                })
416            }
417        }
418
419        impl From<$name> for target_ty!($name $($target_ty)?) {
420            #[inline]
421            fn from(modifier: $name) -> Self {
422                match modifier {
423                    $($name::$variant => target_value!($name $variant $($target_value)?)),*
424                }
425            }
426        }
427    )+};
428}
429
430// Keep in alphabetical order.
431#[automatically_derived]
impl ::core::default::Default for YearRange {
    #[inline]
    fn default() -> YearRange { Self::Extended }
}
impl YearRange {
    /// Parse the modifier from its string representation.
    #[inline]
    fn from_modifier_value(value: &Spanned<&[u8]>)
        -> Result<Option<Self>, Error> {
        if value.eq_ignore_ascii_case(b"standard") {
            return Ok(Some(Self::Standard));
        }
        if value.eq_ignore_ascii_case(b"extended") {
            return Ok(Some(Self::Extended));
        }
        Err(Error {
                _inner: unused(value.span.error("invalid modifier value")),
                public: crate::error::InvalidFormatDescription::InvalidModifier {
                    value: String::from_utf8_lossy(value).into_owned(),
                    index: value.span.start.byte as usize,
                },
            })
    }
}
impl From<YearRange> for crate::format_description::modifier::YearRange {
    #[inline]
    fn from(modifier: YearRange) -> Self {
        match modifier {
            YearRange::Standard =>
                crate::format_description::modifier::YearRange::Standard,
            YearRange::Extended =>
                crate::format_description::modifier::YearRange::Extended,
        }
    }
}modifier! {
432    enum HourBase(bool) {
433        Twelve(true) = b"12",
434        #[default]
435        TwentyFour(false) = b"24",
436    }
437
438    enum MonthCaseSensitive(bool) {
439        False(false) = b"false",
440        #[default]
441        True(true) = b"true",
442    }
443
444    enum MonthRepr {
445        #[default]
446        Numerical = b"numerical",
447        Long = b"long",
448        Short = b"short",
449    }
450
451    enum Padding {
452        Space = b"space",
453        #[default]
454        Zero = b"zero",
455        None = b"none",
456    }
457
458    enum PeriodCase(bool) {
459        Lower(false) = b"lower",
460        #[default]
461        Upper(true) = b"upper",
462    }
463
464    enum PeriodCaseSensitive(bool) {
465        False(false) = b"false",
466        #[default]
467        True(true) = b"true",
468    }
469
470    enum SignBehavior(bool) {
471        #[default]
472        Automatic(false) = b"automatic",
473        Mandatory(true) = b"mandatory",
474    }
475
476    enum SubsecondDigits {
477        One = b"1",
478        Two = b"2",
479        Three = b"3",
480        Four = b"4",
481        Five = b"5",
482        Six = b"6",
483        Seven = b"7",
484        Eight = b"8",
485        Nine = b"9",
486        #[default]
487        OneOrMore = b"1+",
488    }
489
490    enum UnixTimestampPrecision {
491        #[default]
492        Second = b"second",
493        Millisecond = b"millisecond",
494        Microsecond = b"microsecond",
495        Nanosecond = b"nanosecond",
496    }
497
498    enum WeekNumberRepr {
499        #[default]
500        Iso = b"iso",
501        Sunday = b"sunday",
502        Monday = b"monday",
503    }
504
505    enum WeekdayCaseSensitive(bool) {
506        False(false) = b"false",
507        #[default]
508        True(true) = b"true",
509    }
510
511    enum WeekdayOneIndexed(bool) {
512        False(false) = b"false",
513        #[default]
514        True(true) = b"true",
515    }
516
517    enum WeekdayRepr {
518        Short = b"short",
519        #[default]
520        Long = b"long",
521        Sunday = b"sunday",
522        Monday = b"monday",
523    }
524
525    enum YearBase(bool) {
526        #[default]
527        Calendar(false) = b"calendar",
528        IsoWeek(true) = b"iso_week",
529    }
530
531    enum YearRepr {
532        #[default]
533        Full = b"full",
534        Century = b"century",
535        LastTwo = b"last_two",
536    }
537
538    enum YearRange {
539        Standard = b"standard",
540        #[default]
541        Extended = b"extended",
542    }
543}
544
545/// Parse a modifier value using `FromStr`. Requires the modifier value to be valid UTF-8.
546#[inline]
547fn parse_from_modifier_value<T: FromStr>(value: &Spanned<&[u8]>) -> Result<Option<T>, Error> {
548    str::from_utf8(value)
549        .ok()
550        .and_then(|val| val.parse::<T>().ok())
551        .map(|val| Some(val))
552        .ok_or_else(|| Error {
553            _inner: unused(value.span.error("invalid modifier value")),
554            public: crate::error::InvalidFormatDescription::InvalidModifier {
555                value: String::from_utf8_lossy(value).into_owned(),
556                index: value.span.start.byte as usize,
557            },
558        })
559}