1use 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
11pub(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
18pub(super) enum Item<'a> {
20 Literal(&'a [u8]),
22 Component(Component),
24 Optional {
26 value: Box<[Self]>,
28 span: Span,
30 },
31 First {
33 value: Box<[Box<[Self]>]>,
35 span: Span,
37 },
38}
39
40impl Item<'_> {
41 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
159macro_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 fn with_modifiers(
185 modifiers: &[ast::Modifier<'_>],
186 _component_span: Span,
187 ) -> Result<Self, Error>
188 {
189 #[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 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
275component_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
344macro_rules! target_ty {
346 ($name:ident $type:ty) => {
347 $type
348 };
349 ($name:ident) => {
350 $crate::format_description::modifier::$name
351 };
352}
353
354macro_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
364macro_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 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
421modifier! {
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
536fn 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}