diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/time/src/format_description | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/time/src/format_description')
13 files changed, 2252 insertions, 0 deletions
diff --git a/third_party/rust/time/src/format_description/borrowed_format_item.rs b/third_party/rust/time/src/format_description/borrowed_format_item.rs new file mode 100644 index 0000000000..425b902878 --- /dev/null +++ b/third_party/rust/time/src/format_description/borrowed_format_item.rs @@ -0,0 +1,106 @@ +//! A format item with borrowed data. + +#[cfg(feature = "alloc")] +use alloc::string::String; +#[cfg(feature = "alloc")] +use core::fmt; + +use crate::error; +use crate::format_description::Component; + +/// A complete description of how to format and parse a type. +#[non_exhaustive] +#[cfg_attr(not(feature = "alloc"), derive(Debug))] +#[derive(Clone, PartialEq, Eq)] +pub enum BorrowedFormatItem<'a> { + /// Bytes that are formatted as-is. + /// + /// **Note**: If you call the `format` method that returns a `String`, these bytes will be + /// passed through `String::from_utf8_lossy`. + Literal(&'a [u8]), + /// A minimal representation of a single non-literal item. + Component(Component), + /// A series of literals or components that collectively form a partial or complete + /// description. + Compound(&'a [Self]), + /// A `FormatItem` that may or may not be present when parsing. If parsing fails, there + /// will be no effect on the resulting `struct`. + /// + /// This variant has no effect on formatting, as the value is guaranteed to be present. + Optional(&'a Self), + /// A series of `FormatItem`s where, when parsing, the first successful parse is used. When + /// formatting, the first element of the slice is used. An empty slice is a no-op when + /// formatting or parsing. + First(&'a [Self]), +} + +#[cfg(feature = "alloc")] +impl fmt::Debug for BorrowedFormatItem<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Literal(literal) => f.write_str(&String::from_utf8_lossy(literal)), + Self::Component(component) => component.fmt(f), + Self::Compound(compound) => compound.fmt(f), + Self::Optional(item) => f.debug_tuple("Optional").field(item).finish(), + Self::First(items) => f.debug_tuple("First").field(items).finish(), + } + } +} + +impl From<Component> for BorrowedFormatItem<'_> { + fn from(component: Component) -> Self { + Self::Component(component) + } +} + +impl TryFrom<BorrowedFormatItem<'_>> for Component { + type Error = error::DifferentVariant; + + fn try_from(value: BorrowedFormatItem<'_>) -> Result<Self, Self::Error> { + match value { + BorrowedFormatItem::Component(component) => Ok(component), + _ => Err(error::DifferentVariant), + } + } +} + +impl<'a> From<&'a [BorrowedFormatItem<'_>]> for BorrowedFormatItem<'a> { + fn from(items: &'a [BorrowedFormatItem<'_>]) -> Self { + Self::Compound(items) + } +} + +impl<'a> TryFrom<BorrowedFormatItem<'a>> for &[BorrowedFormatItem<'a>] { + type Error = error::DifferentVariant; + + fn try_from(value: BorrowedFormatItem<'a>) -> Result<Self, Self::Error> { + match value { + BorrowedFormatItem::Compound(items) => Ok(items), + _ => Err(error::DifferentVariant), + } + } +} + +impl PartialEq<Component> for BorrowedFormatItem<'_> { + fn eq(&self, rhs: &Component) -> bool { + matches!(self, Self::Component(component) if component == rhs) + } +} + +impl PartialEq<BorrowedFormatItem<'_>> for Component { + fn eq(&self, rhs: &BorrowedFormatItem<'_>) -> bool { + rhs == self + } +} + +impl PartialEq<&[Self]> for BorrowedFormatItem<'_> { + fn eq(&self, rhs: &&[Self]) -> bool { + matches!(self, Self::Compound(compound) if compound == rhs) + } +} + +impl PartialEq<BorrowedFormatItem<'_>> for &[BorrowedFormatItem<'_>] { + fn eq(&self, rhs: &BorrowedFormatItem<'_>) -> bool { + rhs == self + } +} diff --git a/third_party/rust/time/src/format_description/component.rs b/third_party/rust/time/src/format_description/component.rs new file mode 100644 index 0000000000..68d162e260 --- /dev/null +++ b/third_party/rust/time/src/format_description/component.rs @@ -0,0 +1,37 @@ +//! Part of a format description. + +use crate::format_description::modifier; + +/// A component of a larger format description. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Component { + /// Day of the month. + Day(modifier::Day), + /// Month of the year. + Month(modifier::Month), + /// Ordinal day of the year. + Ordinal(modifier::Ordinal), + /// Day of the week. + Weekday(modifier::Weekday), + /// Week within the year. + WeekNumber(modifier::WeekNumber), + /// Year of the date. + Year(modifier::Year), + /// Hour of the day. + Hour(modifier::Hour), + /// Minute within the hour. + Minute(modifier::Minute), + /// AM/PM part of the time. + Period(modifier::Period), + /// Second within the minute. + Second(modifier::Second), + /// Subsecond within the second. + Subsecond(modifier::Subsecond), + /// Hour of the UTC offset. + OffsetHour(modifier::OffsetHour), + /// Minute within the hour of the UTC offset. + OffsetMinute(modifier::OffsetMinute), + /// Second within the minute of the UTC offset. + OffsetSecond(modifier::OffsetSecond), +} diff --git a/third_party/rust/time/src/format_description/mod.rs b/third_party/rust/time/src/format_description/mod.rs new file mode 100644 index 0000000000..7712288e74 --- /dev/null +++ b/third_party/rust/time/src/format_description/mod.rs @@ -0,0 +1,34 @@ +//! Description of how types should be formatted and parsed. +//! +//! The formatted value will be output to the provided writer. Format descriptions can be +//! [well-known](crate::format_description::well_known) or obtained by using the +//! [`format_description!`](crate::macros::format_description) macro, the +//! [`format_description::parse`](crate::format_description::parse()) function. + +mod borrowed_format_item; +mod component; +pub mod modifier; +#[cfg(feature = "alloc")] +mod owned_format_item; +#[cfg(feature = "alloc")] +mod parse; + +pub use borrowed_format_item::BorrowedFormatItem as FormatItem; +#[cfg(feature = "alloc")] +pub use owned_format_item::OwnedFormatItem; + +pub use self::component::Component; +#[cfg(feature = "alloc")] +pub use self::parse::{parse, parse_owned}; + +/// Well-known formats, typically standards. +pub mod well_known { + pub mod iso8601; + mod rfc2822; + mod rfc3339; + + #[doc(inline)] + pub use iso8601::Iso8601; + pub use rfc2822::Rfc2822; + pub use rfc3339::Rfc3339; +} diff --git a/third_party/rust/time/src/format_description/modifier.rs b/third_party/rust/time/src/format_description/modifier.rs new file mode 100644 index 0000000000..e01c18fda4 --- /dev/null +++ b/third_party/rust/time/src/format_description/modifier.rs @@ -0,0 +1,355 @@ +//! Various modifiers for components. + +// region: date modifiers +/// Day of the month. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Day { + /// The padding to obtain the minimum width. + pub padding: Padding, +} + +/// The representation of a month. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum MonthRepr { + /// The number of the month (January is 1, December is 12). + Numerical, + /// The long form of the month name (e.g. "January"). + Long, + /// The short form of the month name (e.g. "Jan"). + Short, +} + +/// Month of the year. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Month { + /// The padding to obtain the minimum width. + pub padding: Padding, + /// What form of representation should be used? + pub repr: MonthRepr, + /// Is the value case sensitive when parsing? + pub case_sensitive: bool, +} + +/// Ordinal day of the year. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Ordinal { + /// The padding to obtain the minimum width. + pub padding: Padding, +} + +/// The representation used for the day of the week. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum WeekdayRepr { + /// The short form of the weekday (e.g. "Mon"). + Short, + /// The long form of the weekday (e.g. "Monday"). + Long, + /// A numerical representation using Sunday as the first day of the week. + /// + /// Sunday is either 0 or 1, depending on the other modifier's value. + Sunday, + /// A numerical representation using Monday as the first day of the week. + /// + /// Monday is either 0 or 1, depending on the other modifier's value. + Monday, +} + +/// Day of the week. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Weekday { + /// What form of representation should be used? + pub repr: WeekdayRepr, + /// When using a numerical representation, should it be zero or one-indexed? + pub one_indexed: bool, + /// Is the value case sensitive when parsing? + pub case_sensitive: bool, +} + +/// The representation used for the week number. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum WeekNumberRepr { + /// Week 1 is the week that contains January 4. + Iso, + /// Week 1 begins on the first Sunday of the calendar year. + Sunday, + /// Week 1 begins on the first Monday of the calendar year. + Monday, +} + +/// Week within the year. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct WeekNumber { + /// The padding to obtain the minimum width. + pub padding: Padding, + /// What kind of representation should be used? + pub repr: WeekNumberRepr, +} + +/// The representation used for a year value. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum YearRepr { + /// The full value of the year. + Full, + /// Only the last two digits of the year. + LastTwo, +} + +/// Year of the date. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Year { + /// The padding to obtain the minimum width. + pub padding: Padding, + /// What kind of representation should be used? + pub repr: YearRepr, + /// Whether the value is based on the ISO week number or the Gregorian calendar. + pub iso_week_based: bool, + /// Whether the `+` sign is present when a positive year contains fewer than five digits. + pub sign_is_mandatory: bool, +} +// endregion date modifiers + +// region: time modifiers +/// Hour of the day. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Hour { + /// The padding to obtain the minimum width. + pub padding: Padding, + /// Is the hour displayed using a 12 or 24-hour clock? + pub is_12_hour_clock: bool, +} + +/// Minute within the hour. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Minute { + /// The padding to obtain the minimum width. + pub padding: Padding, +} + +/// AM/PM part of the time. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Period { + /// Is the period uppercase or lowercase? + pub is_uppercase: bool, + /// Is the value case sensitive when parsing? + /// + /// Note that when `false`, the `is_uppercase` field has no effect on parsing behavior. + pub case_sensitive: bool, +} + +/// Second within the minute. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Second { + /// The padding to obtain the minimum width. + pub padding: Padding, +} + +/// The number of digits present in a subsecond representation. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SubsecondDigits { + /// Exactly one digit. + One, + /// Exactly two digits. + Two, + /// Exactly three digits. + Three, + /// Exactly four digits. + Four, + /// Exactly five digits. + Five, + /// Exactly six digits. + Six, + /// Exactly seven digits. + Seven, + /// Exactly eight digits. + Eight, + /// Exactly nine digits. + Nine, + /// Any number of digits (up to nine) that is at least one. When formatting, the minimum digits + /// necessary will be used. + OneOrMore, +} + +/// Subsecond within the second. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Subsecond { + /// How many digits are present in the component? + pub digits: SubsecondDigits, +} +// endregion time modifiers + +// region: offset modifiers +/// Hour of the UTC offset. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct OffsetHour { + /// Whether the `+` sign is present on positive values. + pub sign_is_mandatory: bool, + /// The padding to obtain the minimum width. + pub padding: Padding, +} + +/// Minute within the hour of the UTC offset. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct OffsetMinute { + /// The padding to obtain the minimum width. + pub padding: Padding, +} + +/// Second within the minute of the UTC offset. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct OffsetSecond { + /// The padding to obtain the minimum width. + pub padding: Padding, +} +// endregion offset modifiers + +/// Type of padding to ensure a minimum width. +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Padding { + /// A space character (` `) should be used as padding. + Space, + /// A zero character (`0`) should be used as padding. + Zero, + /// There is no padding. This can result in a width below the otherwise minimum number of + /// characters. + None, +} + +/// Generate the provided code if and only if `pub` is present. +macro_rules! if_pub { + (pub $(#[$attr:meta])*; $($x:tt)*) => { + $(#[$attr])* + /// + /// This function exists since [`Default::default()`] cannot be used in a `const` context. + /// It may be removed once that becomes possible. As the [`Default`] trait is in the + /// prelude, removing this function in the future will not cause any resolution failures for + /// the overwhelming majority of users; only users who use `#![no_implicit_prelude]` will be + /// affected. As such it will not be considered a breaking change. + $($x)* + }; + ($($_:tt)*) => {}; +} + +/// Implement `Default` for the given type. This also generates an inherent implementation of a +/// `default` method that is `const fn`, permitting the default value to be used in const contexts. +// Every modifier should use this macro rather than a derived `Default`. +macro_rules! impl_const_default { + ($($(#[$doc:meta])* $(@$pub:ident)? $type:ty => $default:expr;)*) => {$( + impl $type { + if_pub! { + $($pub)? + $(#[$doc])*; + pub const fn default() -> Self { + $default + } + } + } + + $(#[$doc])* + impl Default for $type { + fn default() -> Self { + $default + } + } + )*}; +} + +impl_const_default! { + /// Creates a modifier that indicates the value is [padded with zeroes](Padding::Zero). + @pub Day => Self { padding: Padding::Zero }; + /// Creates a modifier that indicates the value uses the + /// [`Numerical`](Self::Numerical) representation. + MonthRepr => Self::Numerical; + /// Creates an instance of this type that indicates the value uses the + /// [`Numerical`](MonthRepr::Numerical) representation, is [padded with zeroes](Padding::Zero), + /// and is case-sensitive when parsing. + @pub Month => Self { + padding: Padding::Zero, + repr: MonthRepr::Numerical, + case_sensitive: true, + }; + /// Creates a modifier that indicates the value is [padded with zeroes](Padding::Zero). + @pub Ordinal => Self { padding: Padding::Zero }; + /// Creates a modifier that indicates the value uses the [`Long`](Self::Long) representation. + WeekdayRepr => Self::Long; + /// Creates a modifier that indicates the value uses the [`Long`](WeekdayRepr::Long) + /// representation and is case-sensitive when parsing. If the representation is changed to a + /// numerical one, the instance defaults to one-based indexing. + @pub Weekday => Self { + repr: WeekdayRepr::Long, + one_indexed: true, + case_sensitive: true, + }; + /// Creates a modifier that indicates that the value uses the [`Iso`](Self::Iso) representation. + WeekNumberRepr => Self::Iso; + /// Creates a modifier that indicates that the value is [padded with zeroes](Padding::Zero) + /// and uses the [`Iso`](WeekNumberRepr::Iso) representation. + @pub WeekNumber => Self { + padding: Padding::Zero, + repr: WeekNumberRepr::Iso, + }; + /// Creates a modifier that indicates the value uses the [`Full`](Self::Full) representation. + YearRepr => Self::Full; + /// Creates a modifier that indicates the value uses the [`Full`](YearRepr::Full) + /// representation, is [padded with zeroes](Padding::Zero), uses the Gregorian calendar as its + /// base, and only includes the year's sign if necessary. + @pub Year => Self { + padding: Padding::Zero, + repr: YearRepr::Full, + iso_week_based: false, + sign_is_mandatory: false, + }; + /// Creates a modifier that indicates the value is [padded with zeroes](Padding::Zero) and + /// has the 24-hour representation. + @pub Hour => Self { + padding: Padding::Zero, + is_12_hour_clock: false, + }; + /// Creates a modifier that indicates the value is [padded with zeroes](Padding::Zero). + @pub Minute => Self { padding: Padding::Zero }; + /// Creates a modifier that indicates the value uses the upper-case representation and is + /// case-sensitive when parsing. + @pub Period => Self { + is_uppercase: true, + case_sensitive: true, + }; + /// Creates a modifier that indicates the value is [padded with zeroes](Padding::Zero). + @pub Second => Self { padding: Padding::Zero }; + /// Creates a modifier that indicates the stringified value contains [one or more + /// digits](Self::OneOrMore). + SubsecondDigits => Self::OneOrMore; + /// Creates a modifier that indicates the stringified value contains [one or more + /// digits](SubsecondDigits::OneOrMore). + @pub Subsecond => Self { digits: SubsecondDigits::OneOrMore }; + /// Creates a modifier that indicates the value uses the `+` sign for all positive values + /// and is [padded with zeroes](Padding::Zero). + @pub OffsetHour => Self { + sign_is_mandatory: true, + padding: Padding::Zero, + }; + /// Creates a modifier that indicates the value is [padded with zeroes](Padding::Zero). + @pub OffsetMinute => Self { padding: Padding::Zero }; + /// Creates a modifier that indicates the value is [padded with zeroes](Padding::Zero). + @pub OffsetSecond => Self { padding: Padding::Zero }; + /// Creates a modifier that indicates the value is [padded with zeroes](Self::Zero). + Padding => Self::Zero; +} diff --git a/third_party/rust/time/src/format_description/owned_format_item.rs b/third_party/rust/time/src/format_description/owned_format_item.rs new file mode 100644 index 0000000000..6f7f7c2337 --- /dev/null +++ b/third_party/rust/time/src/format_description/owned_format_item.rs @@ -0,0 +1,162 @@ +//! A format item with owned data. + +use alloc::boxed::Box; +use alloc::string::String; +use alloc::vec::Vec; +use core::fmt; + +use crate::error; +use crate::format_description::{Component, FormatItem}; + +/// A complete description of how to format and parse a type. +#[non_exhaustive] +#[derive(Clone, PartialEq, Eq)] +pub enum OwnedFormatItem { + /// Bytes that are formatted as-is. + /// + /// **Note**: If you call the `format` method that returns a `String`, these bytes will be + /// passed through `String::from_utf8_lossy`. + Literal(Box<[u8]>), + /// A minimal representation of a single non-literal item. + Component(Component), + /// A series of literals or components that collectively form a partial or complete + /// description. + Compound(Box<[Self]>), + /// A `FormatItem` that may or may not be present when parsing. If parsing fails, there + /// will be no effect on the resulting `struct`. + /// + /// This variant has no effect on formatting, as the value is guaranteed to be present. + Optional(Box<Self>), + /// A series of `FormatItem`s where, when parsing, the first successful parse is used. When + /// formatting, the first element of the [`Vec`] is used. An empty [`Vec`] is a no-op when + /// formatting or parsing. + First(Box<[Self]>), +} + +impl fmt::Debug for OwnedFormatItem { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Literal(literal) => f.write_str(&String::from_utf8_lossy(literal)), + Self::Component(component) => component.fmt(f), + Self::Compound(compound) => compound.fmt(f), + Self::Optional(item) => f.debug_tuple("Optional").field(item).finish(), + Self::First(items) => f.debug_tuple("First").field(items).finish(), + } + } +} + +// region: conversions from FormatItem +impl From<FormatItem<'_>> for OwnedFormatItem { + fn from(item: FormatItem<'_>) -> Self { + (&item).into() + } +} + +impl From<&FormatItem<'_>> for OwnedFormatItem { + fn from(item: &FormatItem<'_>) -> Self { + match item { + FormatItem::Literal(literal) => Self::Literal(literal.to_vec().into_boxed_slice()), + FormatItem::Component(component) => Self::Component(*component), + FormatItem::Compound(compound) => Self::Compound( + compound + .iter() + .cloned() + .map(Into::into) + .collect::<Vec<_>>() + .into_boxed_slice(), + ), + FormatItem::Optional(item) => Self::Optional(Box::new((*item).into())), + FormatItem::First(items) => Self::First( + items + .iter() + .cloned() + .map(Into::into) + .collect::<Vec<_>>() + .into_boxed_slice(), + ), + } + } +} + +impl From<Vec<FormatItem<'_>>> for OwnedFormatItem { + fn from(items: Vec<FormatItem<'_>>) -> Self { + items.as_slice().into() + } +} + +impl<'a, T: AsRef<[FormatItem<'a>]> + ?Sized> From<&T> for OwnedFormatItem { + fn from(items: &T) -> Self { + Self::Compound( + items + .as_ref() + .iter() + .cloned() + .map(Into::into) + .collect::<Vec<_>>() + .into_boxed_slice(), + ) + } +} +// endregion conversions from FormatItem + +// region: from variants +impl From<Component> for OwnedFormatItem { + fn from(component: Component) -> Self { + Self::Component(component) + } +} + +impl TryFrom<OwnedFormatItem> for Component { + type Error = error::DifferentVariant; + + fn try_from(value: OwnedFormatItem) -> Result<Self, Self::Error> { + match value { + OwnedFormatItem::Component(component) => Ok(component), + _ => Err(error::DifferentVariant), + } + } +} + +impl From<Vec<Self>> for OwnedFormatItem { + fn from(items: Vec<Self>) -> Self { + Self::Compound(items.into_boxed_slice()) + } +} + +impl TryFrom<OwnedFormatItem> for Vec<OwnedFormatItem> { + type Error = error::DifferentVariant; + + fn try_from(value: OwnedFormatItem) -> Result<Self, Self::Error> { + match value { + OwnedFormatItem::Compound(items) => Ok(items.into_vec()), + _ => Err(error::DifferentVariant), + } + } +} +// endregion from variants + +// region: equality +impl PartialEq<Component> for OwnedFormatItem { + fn eq(&self, rhs: &Component) -> bool { + matches!(self, Self::Component(component) if component == rhs) + } +} + +impl PartialEq<OwnedFormatItem> for Component { + fn eq(&self, rhs: &OwnedFormatItem) -> bool { + rhs == self + } +} + +impl PartialEq<&[Self]> for OwnedFormatItem { + fn eq(&self, rhs: &&[Self]) -> bool { + matches!(self, Self::Compound(compound) if &&**compound == rhs) + } +} + +impl PartialEq<OwnedFormatItem> for &[OwnedFormatItem] { + fn eq(&self, rhs: &OwnedFormatItem) -> bool { + rhs == self + } +} +// endregion equality diff --git a/third_party/rust/time/src/format_description/parse/ast.rs b/third_party/rust/time/src/format_description/parse/ast.rs new file mode 100644 index 0000000000..6977cc9cda --- /dev/null +++ b/third_party/rust/time/src/format_description/parse/ast.rs @@ -0,0 +1,278 @@ +//! AST for parsing format descriptions. + +use alloc::string::String; +use alloc::vec::Vec; +use core::iter; +use core::iter::Peekable; + +use super::{lexer, Error, Location, Span}; + +/// One part of a complete format description. +#[allow(variant_size_differences)] +pub(super) enum Item<'a> { + /// A literal string, formatted and parsed as-is. + Literal { + /// The string itself. + value: &'a [u8], + /// Where the string originates from in the format string. + _span: Span, + }, + /// A sequence of brackets. The first acts as the escape character. + EscapedBracket { + /// The first bracket. + _first: Location, + /// The second bracket. + _second: Location, + }, + /// Part of a type, along with its modifiers. + Component { + /// Where the opening bracket was in the format string. + _opening_bracket: Location, + /// Whitespace between the opening bracket and name. + _leading_whitespace: Option<Whitespace<'a>>, + /// The name of the component. + name: Name<'a>, + /// The modifiers for the component. + modifiers: Vec<Modifier<'a>>, + /// Whitespace between the modifiers and closing bracket. + _trailing_whitespace: Option<Whitespace<'a>>, + /// Where the closing bracket was in the format string. + _closing_bracket: Location, + }, +} + +/// Whitespace within a component. +pub(super) struct Whitespace<'a> { + /// The whitespace itself. + pub(super) _value: &'a [u8], + /// Where the whitespace was in the format string. + pub(super) span: Span, +} + +/// The name of a component. +pub(super) struct Name<'a> { + /// The name itself. + pub(super) value: &'a [u8], + /// Where the name was in the format string. + pub(super) span: Span, +} + +/// A modifier for a component. +pub(super) struct Modifier<'a> { + /// Whitespace preceding the modifier. + pub(super) _leading_whitespace: Whitespace<'a>, + /// The key of the modifier. + pub(super) key: Key<'a>, + /// Where the colon of the modifier was in the format string. + pub(super) _colon: Location, + /// The value of the modifier. + pub(super) value: Value<'a>, +} + +/// The key of a modifier. +pub(super) struct Key<'a> { + /// The key itself. + pub(super) value: &'a [u8], + /// Where the key was in the format string. + pub(super) span: Span, +} + +/// The value of a modifier. +pub(super) struct Value<'a> { + /// The value itself. + pub(super) value: &'a [u8], + /// Where the value was in the format string. + pub(super) span: Span, +} + +/// Parse the provided tokens into an AST. +pub(super) fn parse<'a>( + tokens: impl Iterator<Item = lexer::Token<'a>>, +) -> impl Iterator<Item = Result<Item<'a>, Error>> { + let mut tokens = tokens.peekable(); + iter::from_fn(move || { + Some(match tokens.next()? { + lexer::Token::Literal { value, span } => Ok(Item::Literal { value, _span: span }), + lexer::Token::Bracket { + kind: lexer::BracketKind::Opening, + location, + } => { + // escaped bracket + if let Some(&lexer::Token::Bracket { + kind: lexer::BracketKind::Opening, + location: second_location, + }) = tokens.peek() + { + tokens.next(); // consume + Ok(Item::EscapedBracket { + _first: location, + _second: second_location, + }) + } + // component + else { + parse_component(location, &mut tokens) + } + } + lexer::Token::Bracket { + kind: lexer::BracketKind::Closing, + location: _, + } => unreachable!( + "internal error: closing bracket should have been consumed by `parse_component`", + ), + lexer::Token::ComponentPart { + kind: _, + value: _, + span: _, + } => unreachable!( + "internal error: component part should have been consumed by `parse_component`", + ), + }) + }) +} + +/// Parse a component. This assumes that the opening bracket has already been consumed. +fn parse_component<'a>( + opening_bracket: Location, + tokens: &mut Peekable<impl Iterator<Item = lexer::Token<'a>>>, +) -> Result<Item<'a>, Error> { + let leading_whitespace = if let Some(&lexer::Token::ComponentPart { + kind: lexer::ComponentKind::Whitespace, + value, + span, + }) = tokens.peek() + { + tokens.next(); // consume + Some(Whitespace { + _value: value, + span, + }) + } else { + None + }; + + let name = if let Some(&lexer::Token::ComponentPart { + kind: lexer::ComponentKind::NotWhitespace, + value, + span, + }) = tokens.peek() + { + tokens.next(); // consume + Name { value, span } + } else { + let span = leading_whitespace.map_or_else( + || Span { + start: opening_bracket, + end: opening_bracket, + }, + |whitespace| whitespace.span.shrink_to_end(), + ); + return Err(Error { + _inner: span.error("expected component name"), + public: crate::error::InvalidFormatDescription::MissingComponentName { + index: span.start_byte(), + }, + }); + }; + + let mut modifiers = Vec::new(); + let trailing_whitespace = loop { + let whitespace = if let Some(&lexer::Token::ComponentPart { + kind: lexer::ComponentKind::Whitespace, + value, + span, + }) = tokens.peek() + { + tokens.next(); // consume + Whitespace { + _value: value, + span, + } + } else { + break None; + }; + + if let Some(&lexer::Token::ComponentPart { + kind: lexer::ComponentKind::NotWhitespace, + value, + span, + }) = tokens.peek() + { + tokens.next(); // consume + + let colon_index = match value.iter().position(|&b| b == b':') { + Some(index) => index, + None => { + return Err(Error { + _inner: span.error("modifier must be of the form `key:value`"), + public: crate::error::InvalidFormatDescription::InvalidModifier { + value: String::from_utf8_lossy(value).into_owned(), + index: span.start_byte(), + }, + }); + } + }; + let key = &value[..colon_index]; + let value = &value[colon_index + 1..]; + + if key.is_empty() { + return Err(Error { + _inner: span.shrink_to_start().error("expected modifier key"), + public: crate::error::InvalidFormatDescription::InvalidModifier { + value: String::new(), + index: span.start_byte(), + }, + }); + } + if value.is_empty() { + return Err(Error { + _inner: span.shrink_to_end().error("expected modifier value"), + public: crate::error::InvalidFormatDescription::InvalidModifier { + value: String::new(), + index: span.shrink_to_end().start_byte(), + }, + }); + } + + modifiers.push(Modifier { + _leading_whitespace: whitespace, + key: Key { + value: key, + span: span.subspan(..colon_index), + }, + _colon: span.start.offset(colon_index), + value: Value { + value, + span: span.subspan(colon_index + 1..), + }, + }); + } else { + break Some(whitespace); + } + }; + + let closing_bracket = if let Some(&lexer::Token::Bracket { + kind: lexer::BracketKind::Closing, + location, + }) = tokens.peek() + { + tokens.next(); // consume + location + } else { + return Err(Error { + _inner: opening_bracket.error("unclosed bracket"), + public: crate::error::InvalidFormatDescription::UnclosedOpeningBracket { + index: opening_bracket.byte, + }, + }); + }; + + Ok(Item::Component { + _opening_bracket: opening_bracket, + _leading_whitespace: leading_whitespace, + name, + modifiers, + _trailing_whitespace: trailing_whitespace, + _closing_bracket: closing_bracket, + }) +} diff --git a/third_party/rust/time/src/format_description/parse/format_item.rs b/third_party/rust/time/src/format_description/parse/format_item.rs new file mode 100644 index 0000000000..53146d5228 --- /dev/null +++ b/third_party/rust/time/src/format_description/parse/format_item.rs @@ -0,0 +1,386 @@ +//! Typed, validated representation of a parsed format description. + +use alloc::string::String; + +use super::{ast, Error}; + +/// Parse an AST iterator into a sequence of format items. +pub(super) fn parse<'a>( + ast_items: impl Iterator<Item = Result<ast::Item<'a>, Error>>, +) -> impl Iterator<Item = Result<Item<'a>, Error>> { + ast_items.map(|ast_item| ast_item.and_then(Item::from_ast)) +} + +/// A description of how to format and parse one part of a type. +#[allow(variant_size_differences)] +pub(super) enum Item<'a> { + /// A literal string. + Literal(&'a [u8]), + /// Part of a type, along with its modifiers. + Component(Component), +} + +impl Item<'_> { + /// Parse an AST item into a format item. + pub(super) fn from_ast(ast_item: ast::Item<'_>) -> Result<Item<'_>, Error> { + Ok(match ast_item { + ast::Item::Component { + _opening_bracket: _, + _leading_whitespace: _, + name, + modifiers, + _trailing_whitespace: _, + _closing_bracket: _, + } => Item::Component(component_from_ast(&name, &modifiers)?), + ast::Item::Literal { value, _span: _ } => Item::Literal(value), + ast::Item::EscapedBracket { + _first: _, + _second: _, + } => Item::Literal(b"["), + }) + } +} + +impl<'a> From<Item<'a>> for crate::format_description::FormatItem<'a> { + fn from(item: Item<'a>) -> Self { + match item { + Item::Literal(literal) => Self::Literal(literal), + Item::Component(component) => Self::Component(component.into()), + } + } +} + +impl From<Item<'_>> for crate::format_description::OwnedFormatItem { + fn from(item: Item<'_>) -> Self { + match item { + Item::Literal(literal) => Self::Literal(literal.to_vec().into_boxed_slice()), + Item::Component(component) => Self::Component(component.into()), + } + } +} + +/// Declare the `Component` struct. +macro_rules! component_definition { + ($vis:vis enum $name:ident { + $($variant:ident = $parse_variant:literal { + $($field:ident = $parse_field:literal: + Option<$field_type:ty> => $target_field:ident),* $(,)? + }),* $(,)? + }) => { + $vis enum $name { + $($variant($variant),)* + } + + $($vis struct $variant { + $($field: Option<$field_type>),* + })* + + $(impl $variant { + /// Parse the component from the AST, given its modifiers. + fn with_modifiers(modifiers: &[ast::Modifier<'_>]) -> Result<Self, Error> { + let mut this = Self { + $($field: None),* + }; + + for modifier in modifiers { + $(if modifier.key.value.eq_ignore_ascii_case($parse_field) { + this.$field = <$field_type>::from_modifier_value(&modifier.value)?; + continue; + })* + return Err(Error { + _inner: modifier.key.span.error("invalid modifier key"), + public: crate::error::InvalidFormatDescription::InvalidModifier { + value: String::from_utf8_lossy(modifier.key.value).into_owned(), + index: modifier.key.span.start_byte(), + } + }); + } + + Ok(this) + } + })* + + impl From<$name> for crate::format_description::Component { + fn from(component: $name) -> Self { + match component {$( + $name::$variant($variant { $($field),* }) => { + $crate::format_description::component::Component::$variant( + $crate::format_description::modifier::$variant {$( + $target_field: $field.unwrap_or_default().into() + ),*} + ) + } + )*} + } + } + + /// Parse a component from the AST, given its name and modifiers. + fn component_from_ast( + name: &ast::Name<'_>, + modifiers: &[ast::Modifier<'_>], + ) -> Result<Component, Error> { + $(if name.value.eq_ignore_ascii_case($parse_variant) { + return Ok(Component::$variant($variant::with_modifiers(&modifiers)?)); + })* + Err(Error { + _inner: name.span.error("invalid component"), + public: crate::error::InvalidFormatDescription::InvalidComponentName { + name: String::from_utf8_lossy(name.value).into_owned(), + index: name.span.start_byte(), + }, + }) + } + } +} + +// Keep in alphabetical order. +component_definition! { + pub(super) enum Component { + Day = b"day" { + padding = b"padding": Option<Padding> => padding, + }, + Hour = b"hour" { + padding = b"padding": Option<Padding> => padding, + base = b"repr": Option<HourBase> => is_12_hour_clock, + }, + Minute = b"minute" { + padding = b"padding": Option<Padding> => padding, + }, + Month = b"month" { + padding = b"padding": Option<Padding> => padding, + repr = b"repr": Option<MonthRepr> => repr, + case_sensitive = b"case_sensitive": Option<MonthCaseSensitive> => case_sensitive, + }, + OffsetHour = b"offset_hour" { + sign_behavior = b"sign": Option<SignBehavior> => sign_is_mandatory, + padding = b"padding": Option<Padding> => padding, + }, + OffsetMinute = b"offset_minute" { + padding = b"padding": Option<Padding> => padding, + }, + OffsetSecond = b"offset_second" { + padding = b"padding": Option<Padding> => padding, + }, + Ordinal = b"ordinal" { + padding = b"padding": Option<Padding> => padding, + }, + Period = b"period" { + case = b"case": Option<PeriodCase> => is_uppercase, + case_sensitive = b"case_sensitive": Option<PeriodCaseSensitive> => case_sensitive, + }, + Second = b"second" { + padding = b"padding": Option<Padding> => padding, + }, + Subsecond = b"subsecond" { + digits = b"digits": Option<SubsecondDigits> => digits, + }, + Weekday = b"weekday" { + repr = b"repr": Option<WeekdayRepr> => repr, + one_indexed = b"one_indexed": Option<WeekdayOneIndexed> => one_indexed, + case_sensitive = b"case_sensitive": Option<WeekdayCaseSensitive> => case_sensitive, + }, + WeekNumber = b"week_number" { + padding = b"padding": Option<Padding> => padding, + repr = b"repr": Option<WeekNumberRepr> => repr, + }, + Year = b"year" { + padding = b"padding": Option<Padding> => padding, + repr = b"repr": Option<YearRepr> => repr, + base = b"base": Option<YearBase> => iso_week_based, + sign_behavior = b"sign": Option<SignBehavior> => sign_is_mandatory, + }, + } +} + +/// Get the target type for a given enum. +macro_rules! target_ty { + ($name:ident $type:ty) => { + $type + }; + ($name:ident) => { + $crate::format_description::modifier::$name + }; +} + +/// Get the target value for a given enum. +macro_rules! target_value { + ($name:ident $variant:ident $value:expr) => { + $value + }; + ($name:ident $variant:ident) => { + $crate::format_description::modifier::$name::$variant + }; +} + +// TODO use `#[derive(Default)]` on enums once MSRV is 1.62 (NET 2022-12-30) +/// Simulate `#[derive(Default)]` on enums. +macro_rules! derived_default_on_enum { + ($type:ty; $default:expr) => {}; + ($attr:meta $type:ty; $default:expr) => { + impl Default for $type { + fn default() -> Self { + $default + } + } + }; +} + +/// Declare the various modifiers. +/// +/// For the general case, ordinary syntax can be used. Note that you _must_ declare a default +/// variant. The only significant change is that the string representation of the variant must be +/// provided after the variant name. For example, `Numerical = b"numerical"` declares a variant +/// named `Numerical` with the string representation `b"numerical"`. This is the value that will be +/// used when parsing the modifier. The value is not case sensitive. +/// +/// If the type in the public API does not have the same name as the type in the internal +/// representation, then the former must be specified in parenthesis after the internal name. For +/// example, `HourBase(bool)` has an internal name "HourBase", but is represented as a boolean in +/// the public API. +/// +/// By default, the internal variant name is assumed to be the same as the public variant name. If +/// this is not the case, the qualified path to the variant must be specified in parenthesis after +/// the internal variant name. For example, `Twelve(true)` has an internal variant name "Twelve", +/// but is represented as `true` in the public API. +macro_rules! modifier { + ($( + enum $name:ident $(($target_ty:ty))? { + $( + $(#[$attr:meta])? + $variant:ident $(($target_value:expr))? = $parse_variant:literal + ),* $(,)? + } + )+) => {$( + enum $name { + $($variant),* + } + + $(derived_default_on_enum! { + $($attr)? $name; $name::$variant + })* + + impl $name { + /// Parse the modifier from its string representation. + fn from_modifier_value(value: &ast::Value<'_>) -> Result<Option<Self>, Error> { + $(if value.value.eq_ignore_ascii_case($parse_variant) { + return Ok(Some(Self::$variant)); + })* + Err(Error { + _inner: value.span.error("invalid modifier value"), + public: crate::error::InvalidFormatDescription::InvalidModifier { + value: String::from_utf8_lossy(value.value).into_owned(), + index: value.span.start_byte(), + }, + }) + } + } + + impl From<$name> for target_ty!($name $($target_ty)?) { + fn from(modifier: $name) -> Self { + match modifier { + $($name::$variant => target_value!($name $variant $($target_value)?)),* + } + } + } + )+}; +} + +// Keep in alphabetical order. +modifier! { + enum HourBase(bool) { + Twelve(true) = b"12", + #[default] + TwentyFour(false) = b"24", + } + + enum MonthCaseSensitive(bool) { + False(false) = b"false", + #[default] + True(true) = b"true", + } + + enum MonthRepr { + #[default] + Numerical = b"numerical", + Long = b"long", + Short = b"short", + } + + enum Padding { + Space = b"space", + #[default] + Zero = b"zero", + None = b"none", + } + + enum PeriodCase(bool) { + Lower(false) = b"lower", + #[default] + Upper(true) = b"upper", + } + + enum PeriodCaseSensitive(bool) { + False(false) = b"false", + #[default] + True(true) = b"true", + } + + enum SignBehavior(bool) { + #[default] + Automatic(false) = b"automatic", + Mandatory(true) = b"mandatory", + } + + enum SubsecondDigits { + One = b"1", + Two = b"2", + Three = b"3", + Four = b"4", + Five = b"5", + Six = b"6", + Seven = b"7", + Eight = b"8", + Nine = b"9", + #[default] + OneOrMore = b"1+", + } + + enum WeekNumberRepr { + #[default] + Iso = b"iso", + Sunday = b"sunday", + Monday = b"monday", + } + + enum WeekdayCaseSensitive(bool) { + False(false) = b"false", + #[default] + True(true) = b"true", + } + + enum WeekdayOneIndexed(bool) { + False(false) = b"false", + #[default] + True(true) = b"true", + } + + enum WeekdayRepr { + Short = b"short", + #[default] + Long = b"long", + Sunday = b"sunday", + Monday = b"monday", + } + + enum YearBase(bool) { + #[default] + Calendar(false) = b"calendar", + IsoWeek(true) = b"iso_week", + } + + enum YearRepr { + #[default] + Full = b"full", + LastTwo = b"last_two", + } +} diff --git a/third_party/rust/time/src/format_description/parse/lexer.rs b/third_party/rust/time/src/format_description/parse/lexer.rs new file mode 100644 index 0000000000..e405ea8c85 --- /dev/null +++ b/third_party/rust/time/src/format_description/parse/lexer.rs @@ -0,0 +1,159 @@ +//! Lexer for parsing format descriptions. + +use core::iter; + +use super::{Location, Span}; + +/// A token emitted by the lexer. There is no semantic meaning at this stage. +pub(super) enum Token<'a> { + /// A literal string, formatted and parsed as-is. + Literal { + /// The string itself. + value: &'a [u8], + /// Where the string was in the format string. + span: Span, + }, + /// An opening or closing bracket. May or may not be the start or end of a component. + Bracket { + /// Whether the bracket is opening or closing. + kind: BracketKind, + /// Where the bracket was in the format string. + location: Location, + }, + /// One part of a component. This could be its name, a modifier, or whitespace. + ComponentPart { + /// Whether the part is whitespace or not. + kind: ComponentKind, + /// The part itself. + value: &'a [u8], + /// Where the part was in the format string. + span: Span, + }, +} + +/// What type of bracket is present. +pub(super) enum BracketKind { + /// An opening bracket: `[` + Opening, + /// A closing bracket: `]` + Closing, +} + +/// Indicates whether the component is whitespace or not. +pub(super) enum ComponentKind { + #[allow(clippy::missing_docs_in_private_items)] + Whitespace, + #[allow(clippy::missing_docs_in_private_items)] + NotWhitespace, +} + +/// Attach [`Location`] information to each byte in the iterator. +fn attach_location(iter: impl Iterator<Item = u8>) -> impl Iterator<Item = (u8, Location)> { + let mut line = 1; + let mut column = 1; + let mut byte_pos = 0; + + iter.map(move |byte| { + let location = Location { + line, + column, + byte: byte_pos, + }; + column += 1; + byte_pos += 1; + + if byte == b'\n' { + line += 1; + column = 1; + } + + (byte, location) + }) +} + +/// Parse the string into a series of [`Token`]s. +pub(super) fn lex(mut input: &[u8]) -> impl Iterator<Item = Token<'_>> { + let mut depth: u8 = 0; + let mut iter = attach_location(input.iter().copied()).peekable(); + let mut second_bracket_location = None; + + iter::from_fn(move || { + // There is a flag set to emit the second half of an escaped bracket pair. + if let Some(location) = second_bracket_location.take() { + return Some(Token::Bracket { + kind: BracketKind::Opening, + location, + }); + } + + Some(match iter.next()? { + (b'[', location) => { + if let Some((_, second_location)) = iter.next_if(|&(byte, _)| byte == b'[') { + // escaped bracket + second_bracket_location = Some(second_location); + input = &input[2..]; + } else { + // opening bracket + depth += 1; + input = &input[1..]; + } + + Token::Bracket { + kind: BracketKind::Opening, + location, + } + } + // closing bracket + (b']', location) if depth > 0 => { + depth -= 1; + input = &input[1..]; + Token::Bracket { + kind: BracketKind::Closing, + location, + } + } + // literal + (_, start_location) if depth == 0 => { + let mut bytes = 1; + let mut end_location = start_location; + + while let Some((_, location)) = iter.next_if(|&(byte, _)| byte != b'[') { + end_location = location; + bytes += 1; + } + + let value = &input[..bytes]; + input = &input[bytes..]; + Token::Literal { + value, + span: Span::start_end(start_location, end_location), + } + } + // component part + (byte, start_location) => { + let mut bytes = 1; + let mut end_location = start_location; + let is_whitespace = byte.is_ascii_whitespace(); + + while let Some((_, location)) = iter.next_if(|&(byte, _)| { + byte != b'[' && byte != b']' && is_whitespace == byte.is_ascii_whitespace() + }) { + end_location = location; + bytes += 1; + } + + let value = &input[..bytes]; + input = &input[bytes..]; + Token::ComponentPart { + kind: if is_whitespace { + ComponentKind::Whitespace + } else { + ComponentKind::NotWhitespace + }, + value, + span: Span::start_end(start_location, end_location), + } + } + }) + }) +} diff --git a/third_party/rust/time/src/format_description/parse/mod.rs b/third_party/rust/time/src/format_description/parse/mod.rs new file mode 100644 index 0000000000..c73a674494 --- /dev/null +++ b/third_party/rust/time/src/format_description/parse/mod.rs @@ -0,0 +1,193 @@ +//! Parser for format descriptions. + +use alloc::vec::Vec; +use core::ops::{RangeFrom, RangeTo}; + +mod ast; +mod format_item; +mod lexer; + +/// Parse a sequence of items from the format description. +/// +/// The syntax for the format description can be found in [the +/// book](https://time-rs.github.io/book/api/format-description.html). +pub fn parse( + s: &str, +) -> Result<Vec<crate::format_description::FormatItem<'_>>, crate::error::InvalidFormatDescription> +{ + let lexed = lexer::lex(s.as_bytes()); + let ast = ast::parse(lexed); + let format_items = format_item::parse(ast); + Ok(format_items + .map(|res| res.map(Into::into)) + .collect::<Result<Vec<_>, _>>()?) +} + +/// Parse a sequence of items from the format description. +/// +/// The syntax for the format description can be found in [the +/// book](https://time-rs.github.io/book/api/format-description.html). +/// +/// Unlike [`parse`], this function returns [`OwnedFormatItem`], which owns its contents. This means +/// that there is no lifetime that needs to be handled. +/// +/// [`OwnedFormatItem`]: crate::format_description::OwnedFormatItem +pub fn parse_owned( + s: &str, +) -> Result<crate::format_description::OwnedFormatItem, crate::error::InvalidFormatDescription> { + let lexed = lexer::lex(s.as_bytes()); + let ast = ast::parse(lexed); + let format_items = format_item::parse(ast); + let items = format_items + .map(|res| res.map(Into::into)) + .collect::<Result<Vec<_>, _>>()? + .into_boxed_slice(); + Ok(crate::format_description::OwnedFormatItem::Compound(items)) +} + +/// A location within a string. +#[derive(Clone, Copy)] +struct Location { + /// The one-indexed line of the string. + line: usize, + /// The one-indexed column of the string. + column: usize, + /// The zero-indexed byte of the string. + byte: usize, +} + +impl Location { + /// Offset the location by the provided amount. + /// + /// Note that this assumes the resulting location is on the same line as the original location. + #[must_use = "this does not modify the original value"] + const fn offset(&self, offset: usize) -> Self { + Self { + line: self.line, + column: self.column + offset, + byte: self.byte + offset, + } + } + + /// Create an error with the provided message at this location. + const fn error(self, message: &'static str) -> ErrorInner { + ErrorInner { + _message: message, + _span: Span { + start: self, + end: self, + }, + } + } +} + +/// A start and end point within a string. +#[derive(Clone, Copy)] +struct Span { + #[allow(clippy::missing_docs_in_private_items)] + start: Location, + #[allow(clippy::missing_docs_in_private_items)] + end: Location, +} + +impl Span { + /// Create a new `Span` from the provided start and end locations. + const fn start_end(start: Location, end: Location) -> Self { + Self { start, end } + } + + /// Reduce this span to the provided range. + #[must_use = "this does not modify the original value"] + fn subspan(&self, range: impl Subspan) -> Self { + range.subspan(self) + } + + /// Obtain a `Span` pointing at the start of the pre-existing span. + #[must_use = "this does not modify the original value"] + const fn shrink_to_start(&self) -> Self { + Self { + start: self.start, + end: self.start, + } + } + + /// Obtain a `Span` pointing at the end of the pre-existing span. + #[must_use = "this does not modify the original value"] + const fn shrink_to_end(&self) -> Self { + Self { + start: self.end, + end: self.end, + } + } + + /// Create an error with the provided message at this span. + const fn error(self, message: &'static str) -> ErrorInner { + ErrorInner { + _message: message, + _span: self, + } + } + + /// Get the byte index that the span starts at. + const fn start_byte(&self) -> usize { + self.start.byte + } +} + +/// A trait for types that can be used to reduce a `Span`. +trait Subspan { + /// Reduce the provided `Span` to a new `Span`. + fn subspan(self, span: &Span) -> Span; +} + +impl Subspan for RangeFrom<usize> { + fn subspan(self, span: &Span) -> Span { + assert_eq!(span.start.line, span.end.line); + + Span { + start: Location { + line: span.start.line, + column: span.start.column + self.start, + byte: span.start.byte + self.start, + }, + end: span.end, + } + } +} + +impl Subspan for RangeTo<usize> { + fn subspan(self, span: &Span) -> Span { + assert_eq!(span.start.line, span.end.line); + + Span { + start: span.start, + end: Location { + line: span.start.line, + column: span.start.column + self.end - 1, + byte: span.start.byte + self.end - 1, + }, + } + } +} + +/// The internal error type. +struct ErrorInner { + /// The message displayed to the user. + _message: &'static str, + /// Where the error originated. + _span: Span, +} + +/// A complete error description. +struct Error { + /// The internal error. + _inner: ErrorInner, + /// The error needed for interoperability with the rest of `time`. + public: crate::error::InvalidFormatDescription, +} + +impl From<Error> for crate::error::InvalidFormatDescription { + fn from(error: Error) -> Self { + error.public + } +} diff --git a/third_party/rust/time/src/format_description/well_known/iso8601.rs b/third_party/rust/time/src/format_description/well_known/iso8601.rs new file mode 100644 index 0000000000..f19181a926 --- /dev/null +++ b/third_party/rust/time/src/format_description/well_known/iso8601.rs @@ -0,0 +1,233 @@ +//! The format described in ISO 8601. + +mod adt_hack; + +use core::num::NonZeroU8; + +pub use self::adt_hack::{DoNotRelyOnWhatThisIs, EncodedConfig}; + +/// A configuration for [`Iso8601`] that only parses values. +const PARSING_ONLY: EncodedConfig = Config { + formatted_components: FormattedComponents::None, + use_separators: false, + year_is_six_digits: false, + date_kind: DateKind::Calendar, + time_precision: TimePrecision::Hour { + decimal_digits: None, + }, + offset_precision: OffsetPrecision::Hour, +} +.encode(); + +/// The default configuration for [`Iso8601`]. +const DEFAULT_CONFIG: EncodedConfig = Config::DEFAULT.encode(); + +/// The format described in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html). +/// +/// This implementation is of ISO 8601-1:2019. It may not be compatible with other versions. +/// +/// The const parameter `CONFIG` **must** be a value that was returned by [`Config::encode`]. +/// Passing any other value is **unspecified behavior**. +/// +/// Example: 1997-11-21T09:55:06.000000000-06:00 +/// +/// # Examples +#[cfg_attr(feature = "formatting", doc = "```rust")] +#[cfg_attr(not(feature = "formatting"), doc = "```rust,ignore")] +/// # use time::format_description::well_known::Iso8601; +/// # use time_macros::datetime; +/// assert_eq!( +/// datetime!(1997-11-12 9:55:06 -6:00).format(&Iso8601::DEFAULT)?, +/// "1997-11-12T09:55:06.000000000-06:00" +/// ); +/// # Ok::<_, time::Error>(()) +/// ``` +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct Iso8601<const CONFIG: EncodedConfig = DEFAULT_CONFIG>; + +impl<const CONFIG: EncodedConfig> core::fmt::Debug for Iso8601<CONFIG> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("Iso8601") + .field("config", &Config::decode(CONFIG)) + .finish() + } +} + +impl Iso8601<DEFAULT_CONFIG> { + /// An [`Iso8601`] with the default configuration. + /// + /// The following is the default behavior: + /// + /// - The configuration can be used for both formatting and parsing. + /// - The date, time, and UTC offset are all formatted. + /// - Separators (such as `-` and `:`) are included. + /// - The year contains four digits, such that the year must be between 0 and 9999. + /// - The date uses the calendar format. + /// - The time has precision to the second and nine decimal digits. + /// - The UTC offset has precision to the minute. + /// + /// If you need different behavior, use [`Config::DEFAULT`] and [`Config`]'s methods to create + /// a custom configuration. + pub const DEFAULT: Self = Self; +} + +impl Iso8601<PARSING_ONLY> { + /// An [`Iso8601`] that can only be used for parsing. Using this to format a value is + /// unspecified behavior. + pub const PARSING: Self = Self; +} + +/// Which components to format. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FormattedComponents { + /// The configuration can only be used for parsing. Using this to format a value is + /// unspecified behavior. + None, + /// Format only the date. + Date, + /// Format only the time. + Time, + /// Format only the UTC offset. + Offset, + /// Format the date and time. + DateTime, + /// Format the date, time, and UTC offset. + DateTimeOffset, + /// Format the time and UTC offset. + TimeOffset, +} + +/// Which format to use for the date. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DateKind { + /// Use the year-month-day format. + Calendar, + /// Use the year-week-weekday format. + Week, + /// Use the week-ordinal format. + Ordinal, +} + +/// The precision and number of decimal digits present for the time. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TimePrecision { + /// Format the hour only. Minutes, seconds, and nanoseconds will be represented with the + /// specified number of decimal digits, if any. + Hour { + #[allow(clippy::missing_docs_in_private_items)] + decimal_digits: Option<NonZeroU8>, + }, + /// Format the hour and minute. Seconds and nanoseconds will be represented with the specified + /// number of decimal digits, if any. + Minute { + #[allow(clippy::missing_docs_in_private_items)] + decimal_digits: Option<NonZeroU8>, + }, + /// Format the hour, minute, and second. Nanoseconds will be represented with the specified + /// number of decimal digits, if any. + Second { + #[allow(clippy::missing_docs_in_private_items)] + decimal_digits: Option<NonZeroU8>, + }, +} + +/// The precision for the UTC offset. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum OffsetPrecision { + /// Format only the offset hour. Requires the offset minute to be zero. + Hour, + /// Format both the offset hour and minute. + Minute, +} + +/// Configuration for [`Iso8601`]. +// This is only used as a const generic, so there's no need to have a number of implementations on +// it. +#[allow(missing_copy_implementations)] +#[doc(alias = "EncodedConfig")] // People will likely search for `EncodedConfig`, so show them this. +#[derive(Debug)] +pub struct Config { + /// Which components, if any, will be formatted. + pub(crate) formatted_components: FormattedComponents, + /// Whether the format contains separators (such as `-` or `:`). + pub(crate) use_separators: bool, + /// Whether the year is six digits. + pub(crate) year_is_six_digits: bool, + /// The format used for the date. + pub(crate) date_kind: DateKind, + /// The precision and number of decimal digits present for the time. + pub(crate) time_precision: TimePrecision, + /// The precision for the UTC offset. + pub(crate) offset_precision: OffsetPrecision, +} + +impl Config { + /// A configuration for the [`Iso8601`] format. + /// + /// The following is the default behavior: + /// + /// - The configuration can be used for both formatting and parsing. + /// - The date, time, and UTC offset are all formatted. + /// - Separators (such as `-` and `:`) are included. + /// - The year contains four digits, such that the year must be between 0 and 9999. + /// - The date uses the calendar format. + /// - The time has precision to the second and nine decimal digits. + /// - The UTC offset has precision to the minute. + /// + /// If you need different behavior, use the setter methods on this struct. + pub const DEFAULT: Self = Self { + formatted_components: FormattedComponents::DateTimeOffset, + use_separators: true, + year_is_six_digits: false, + date_kind: DateKind::Calendar, + time_precision: TimePrecision::Second { + decimal_digits: NonZeroU8::new(9), + }, + offset_precision: OffsetPrecision::Minute, + }; + + /// Set whether the format the date, time, and/or UTC offset. + pub const fn set_formatted_components(self, formatted_components: FormattedComponents) -> Self { + Self { + formatted_components, + ..self + } + } + + /// Set whether the format contains separators (such as `-` or `:`). + pub const fn set_use_separators(self, use_separators: bool) -> Self { + Self { + use_separators, + ..self + } + } + + /// Set whether the year is six digits. + pub const fn set_year_is_six_digits(self, year_is_six_digits: bool) -> Self { + Self { + year_is_six_digits, + ..self + } + } + + /// Set the format used for the date. + pub const fn set_date_kind(self, date_kind: DateKind) -> Self { + Self { date_kind, ..self } + } + + /// Set the precision and number of decimal digits present for the time. + pub const fn set_time_precision(self, time_precision: TimePrecision) -> Self { + Self { + time_precision, + ..self + } + } + + /// Set the precision for the UTC offset. + pub const fn set_offset_precision(self, offset_precision: OffsetPrecision) -> Self { + Self { + offset_precision, + ..self + } + } +} diff --git a/third_party/rust/time/src/format_description/well_known/iso8601/adt_hack.rs b/third_party/rust/time/src/format_description/well_known/iso8601/adt_hack.rs new file mode 100644 index 0000000000..757a68b18f --- /dev/null +++ b/third_party/rust/time/src/format_description/well_known/iso8601/adt_hack.rs @@ -0,0 +1,249 @@ +//! Hackery to work around not being able to use ADTs in const generics on stable. + +use core::num::NonZeroU8; + +#[cfg(feature = "formatting")] +use super::Iso8601; +use super::{Config, DateKind, FormattedComponents as FC, OffsetPrecision, TimePrecision}; + +// This provides a way to include `EncodedConfig` in documentation without displaying the type it is +// aliased to. +#[doc(hidden)] +pub type DoNotRelyOnWhatThisIs = u128; + +/// An encoded [`Config`] that can be used as a const parameter to [`Iso8601`]. +/// +/// The type this is aliased to must not be relied upon. It can change in any release without +/// notice. +pub type EncodedConfig = DoNotRelyOnWhatThisIs; + +#[cfg(feature = "formatting")] +impl<const CONFIG: EncodedConfig> Iso8601<CONFIG> { + /// The user-provided configuration for the ISO 8601 format. + const CONFIG: Config = Config::decode(CONFIG); + /// Whether the date should be formatted. + pub(crate) const FORMAT_DATE: bool = matches!( + Self::CONFIG.formatted_components, + FC::Date | FC::DateTime | FC::DateTimeOffset + ); + /// Whether the time should be formatted. + pub(crate) const FORMAT_TIME: bool = matches!( + Self::CONFIG.formatted_components, + FC::Time | FC::DateTime | FC::DateTimeOffset | FC::TimeOffset + ); + /// Whether the UTC offset should be formatted. + pub(crate) const FORMAT_OFFSET: bool = matches!( + Self::CONFIG.formatted_components, + FC::Offset | FC::DateTimeOffset | FC::TimeOffset + ); + /// Whether the year is six digits. + pub(crate) const YEAR_IS_SIX_DIGITS: bool = Self::CONFIG.year_is_six_digits; + /// Whether the format contains separators (such as `-` or `:`). + pub(crate) const USE_SEPARATORS: bool = Self::CONFIG.use_separators; + /// Which format to use for the date. + pub(crate) const DATE_KIND: DateKind = Self::CONFIG.date_kind; + /// The precision and number of decimal digits to use for the time. + pub(crate) const TIME_PRECISION: TimePrecision = Self::CONFIG.time_precision; + /// The precision for the UTC offset. + pub(crate) const OFFSET_PRECISION: OffsetPrecision = Self::CONFIG.offset_precision; +} + +impl Config { + /// Encode the configuration, permitting it to be used as a const parameter of [`Iso8601`]. + /// + /// The value returned by this method must only be used as a const parameter to [`Iso8601`]. Any + /// other usage is unspecified behavior. + pub const fn encode(&self) -> EncodedConfig { + let mut bytes = [0; EncodedConfig::BITS as usize / 8]; + + bytes[0] = match self.formatted_components { + FC::None => 0, + FC::Date => 1, + FC::Time => 2, + FC::Offset => 3, + FC::DateTime => 4, + FC::DateTimeOffset => 5, + FC::TimeOffset => 6, + }; + bytes[1] = self.use_separators as _; + bytes[2] = self.year_is_six_digits as _; + bytes[3] = match self.date_kind { + DateKind::Calendar => 0, + DateKind::Week => 1, + DateKind::Ordinal => 2, + }; + bytes[4] = match self.time_precision { + TimePrecision::Hour { .. } => 0, + TimePrecision::Minute { .. } => 1, + TimePrecision::Second { .. } => 2, + }; + bytes[5] = match self.time_precision { + TimePrecision::Hour { decimal_digits } + | TimePrecision::Minute { decimal_digits } + | TimePrecision::Second { decimal_digits } => match decimal_digits { + None => 0, + Some(decimal_digits) => decimal_digits.get(), + }, + }; + bytes[6] = match self.offset_precision { + OffsetPrecision::Hour => 0, + OffsetPrecision::Minute => 1, + }; + + EncodedConfig::from_be_bytes(bytes) + } + + /// Decode the configuration. The configuration must have been generated from + /// [`Config::encode`]. + pub(super) const fn decode(encoded: EncodedConfig) -> Self { + let bytes = encoded.to_be_bytes(); + + let formatted_components = match bytes[0] { + 0 => FC::None, + 1 => FC::Date, + 2 => FC::Time, + 3 => FC::Offset, + 4 => FC::DateTime, + 5 => FC::DateTimeOffset, + 6 => FC::TimeOffset, + _ => panic!("invalid configuration"), + }; + let use_separators = match bytes[1] { + 0 => false, + 1 => true, + _ => panic!("invalid configuration"), + }; + let year_is_six_digits = match bytes[2] { + 0 => false, + 1 => true, + _ => panic!("invalid configuration"), + }; + let date_kind = match bytes[3] { + 0 => DateKind::Calendar, + 1 => DateKind::Week, + 2 => DateKind::Ordinal, + _ => panic!("invalid configuration"), + }; + let time_precision = match bytes[4] { + 0 => TimePrecision::Hour { + decimal_digits: NonZeroU8::new(bytes[5]), + }, + 1 => TimePrecision::Minute { + decimal_digits: NonZeroU8::new(bytes[5]), + }, + 2 => TimePrecision::Second { + decimal_digits: NonZeroU8::new(bytes[5]), + }, + _ => panic!("invalid configuration"), + }; + let offset_precision = match bytes[6] { + 0 => OffsetPrecision::Hour, + 1 => OffsetPrecision::Minute, + _ => panic!("invalid configuration"), + }; + + // No `for` loops in `const fn`. + let mut idx = 7; // first unused byte + while idx < EncodedConfig::BITS as usize / 8 { + assert!(bytes[idx] == 0, "invalid configuration"); + idx += 1; + } + + Self { + formatted_components, + use_separators, + year_is_six_digits, + date_kind, + time_precision, + offset_precision, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + macro_rules! eq { + ($a:expr, $b:expr) => {{ + let a = $a; + let b = $b; + a.formatted_components == b.formatted_components + && a.use_separators == b.use_separators + && a.year_is_six_digits == b.year_is_six_digits + && a.date_kind == b.date_kind + && a.time_precision == b.time_precision + && a.offset_precision == b.offset_precision + }}; + } + + #[test] + fn encoding_roundtrip() { + macro_rules! assert_roundtrip { + ($config:expr) => { + let config = $config; + let encoded = config.encode(); + let decoded = Config::decode(encoded); + assert!(eq!(config, decoded)); + }; + } + + assert_roundtrip!(Config::DEFAULT); + assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::None)); + assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::Date)); + assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::Time)); + assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::Offset)); + assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::DateTime)); + assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::DateTimeOffset)); + assert_roundtrip!(Config::DEFAULT.set_formatted_components(FC::TimeOffset)); + assert_roundtrip!(Config::DEFAULT.set_use_separators(false)); + assert_roundtrip!(Config::DEFAULT.set_use_separators(true)); + assert_roundtrip!(Config::DEFAULT.set_year_is_six_digits(false)); + assert_roundtrip!(Config::DEFAULT.set_year_is_six_digits(true)); + assert_roundtrip!(Config::DEFAULT.set_date_kind(DateKind::Calendar)); + assert_roundtrip!(Config::DEFAULT.set_date_kind(DateKind::Week)); + assert_roundtrip!(Config::DEFAULT.set_date_kind(DateKind::Ordinal)); + assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Hour { + decimal_digits: None, + })); + assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Minute { + decimal_digits: None, + })); + assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Second { + decimal_digits: None, + })); + assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Hour { + decimal_digits: NonZeroU8::new(1), + })); + assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Minute { + decimal_digits: NonZeroU8::new(1), + })); + assert_roundtrip!(Config::DEFAULT.set_time_precision(TimePrecision::Second { + decimal_digits: NonZeroU8::new(1), + })); + assert_roundtrip!(Config::DEFAULT.set_offset_precision(OffsetPrecision::Hour)); + assert_roundtrip!(Config::DEFAULT.set_offset_precision(OffsetPrecision::Minute)); + } + + macro_rules! assert_decode_fail { + ($encoding:expr) => { + assert!( + std::panic::catch_unwind(|| { + Config::decode($encoding); + }) + .is_err() + ); + }; + } + + #[test] + fn decode_fail() { + assert_decode_fail!(0x07_00_00_00_00_00_00_00_00_00_00_00_00_00_00_00); + assert_decode_fail!(0x00_02_00_00_00_00_00_00_00_00_00_00_00_00_00_00); + assert_decode_fail!(0x00_00_02_00_00_00_00_00_00_00_00_00_00_00_00_00); + assert_decode_fail!(0x00_00_00_03_00_00_00_00_00_00_00_00_00_00_00_00); + assert_decode_fail!(0x00_00_00_00_03_00_00_00_00_00_00_00_00_00_00_00); + assert_decode_fail!(0x00_00_00_00_00_00_02_00_00_00_00_00_00_00_00_00); + assert_decode_fail!(0x00_00_00_00_00_00_00_01_00_00_00_00_00_00_00_00); + } +} diff --git a/third_party/rust/time/src/format_description/well_known/rfc2822.rs b/third_party/rust/time/src/format_description/well_known/rfc2822.rs new file mode 100644 index 0000000000..3c890ab107 --- /dev/null +++ b/third_party/rust/time/src/format_description/well_known/rfc2822.rs @@ -0,0 +1,30 @@ +//! The format described in RFC 2822. + +/// The format described in [RFC 2822](https://tools.ietf.org/html/rfc2822#section-3.3). +/// +/// Example: Fri, 21 Nov 1997 09:55:06 -0600 +/// +/// # Examples +#[cfg_attr(feature = "parsing", doc = "```rust")] +#[cfg_attr(not(feature = "parsing"), doc = "```rust,ignore")] +/// # use time::{format_description::well_known::Rfc2822, OffsetDateTime}; +/// use time_macros::datetime; +/// assert_eq!( +/// OffsetDateTime::parse("Sat, 12 Jun 1993 13:25:19 GMT", &Rfc2822)?, +/// datetime!(1993-06-12 13:25:19 +00:00) +/// ); +/// # Ok::<_, time::Error>(()) +/// ``` +/// +#[cfg_attr(feature = "formatting", doc = "```rust")] +#[cfg_attr(not(feature = "formatting"), doc = "```rust,ignore")] +/// # use time::format_description::well_known::Rfc2822; +/// # use time_macros::datetime; +/// assert_eq!( +/// datetime!(1997-11-21 09:55:06 -06:00).format(&Rfc2822)?, +/// "Fri, 21 Nov 1997 09:55:06 -0600" +/// ); +/// # Ok::<_, time::Error>(()) +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Rfc2822; diff --git a/third_party/rust/time/src/format_description/well_known/rfc3339.rs b/third_party/rust/time/src/format_description/well_known/rfc3339.rs new file mode 100644 index 0000000000..f0873cbac5 --- /dev/null +++ b/third_party/rust/time/src/format_description/well_known/rfc3339.rs @@ -0,0 +1,30 @@ +//! The format described in RFC 3339. + +/// The format described in [RFC 3339](https://tools.ietf.org/html/rfc3339#section-5.6). +/// +/// Format example: 1985-04-12T23:20:50.52Z +/// +/// # Examples +#[cfg_attr(feature = "parsing", doc = "```rust")] +#[cfg_attr(not(feature = "parsing"), doc = "```rust,ignore")] +/// # use time::{format_description::well_known::Rfc3339, OffsetDateTime}; +/// # use time_macros::datetime; +/// assert_eq!( +/// OffsetDateTime::parse("1985-04-12T23:20:50.52Z", &Rfc3339)?, +/// datetime!(1985-04-12 23:20:50.52 +00:00) +/// ); +/// # Ok::<_, time::Error>(()) +/// ``` +/// +#[cfg_attr(feature = "formatting", doc = "```rust")] +#[cfg_attr(not(feature = "formatting"), doc = "```rust,ignore")] +/// # use time::format_description::well_known::Rfc3339; +/// # use time_macros::datetime; +/// assert_eq!( +/// datetime!(1985-04-12 23:20:50.52 +00:00).format(&Rfc3339)?, +/// "1985-04-12T23:20:50.52Z" +/// ); +/// # Ok::<_, time::Error>(()) +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Rfc3339; |