diff options
Diffstat (limited to 'third_party/rust/derive_more-impl/src/fmt/parsing.rs')
-rw-r--r-- | third_party/rust/derive_more-impl/src/fmt/parsing.rs | 1096 |
1 files changed, 1096 insertions, 0 deletions
diff --git a/third_party/rust/derive_more-impl/src/fmt/parsing.rs b/third_party/rust/derive_more-impl/src/fmt/parsing.rs new file mode 100644 index 0000000000..e4586668f0 --- /dev/null +++ b/third_party/rust/derive_more-impl/src/fmt/parsing.rs @@ -0,0 +1,1096 @@ +//! Parsing of [Rust `fmt` syntax][0]. +//! +//! [0]: std::fmt#syntax + +use std::{convert::identity, iter}; + +use unicode_xid::UnicodeXID as XID; + +/// Output of the [`format_string`] parser. +#[derive(Debug, Clone, Eq, PartialEq)] +pub(crate) struct FormatString<'a> { + pub(crate) formats: Vec<Format<'a>>, +} + +/// Output of the [`format`] parser. +/// +/// [`format`]: fn@format +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub(crate) struct Format<'a> { + pub(crate) arg: Option<Argument<'a>>, + pub(crate) spec: Option<FormatSpec<'a>>, +} + +/// Output of the [`format_spec`] parser. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub(crate) struct FormatSpec<'a> { + pub(crate) width: Option<Width<'a>>, + pub(crate) precision: Option<Precision<'a>>, + pub(crate) ty: Type, +} + +/// Output of the [`argument`] parser. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub(crate) enum Argument<'a> { + Integer(usize), + Identifier(&'a str), +} + +/// Output of the [`precision`] parser. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub(crate) enum Precision<'a> { + Count(Count<'a>), + Star, +} + +/// Output of the [`count`] parser. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub(crate) enum Count<'a> { + Integer(usize), + Parameter(Parameter<'a>), +} + +/// Output of the [`type_`] parser. See [formatting traits][1] for more info. +/// +/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#formatting-traits +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub(crate) enum Type { + Display, + Debug, + LowerDebug, + UpperDebug, + Octal, + LowerHex, + UpperHex, + Pointer, + Binary, + LowerExp, + UpperExp, +} + +impl Type { + /// Returns trait name of this [`Type`]. + pub(crate) fn trait_name(&self) -> &'static str { + match self { + Type::Display => "Display", + Type::Debug | Type::LowerDebug | Type::UpperDebug => "Debug", + Type::Octal => "Octal", + Type::LowerHex => "LowerHex", + Type::UpperHex => "UpperHex", + Type::Pointer => "Pointer", + Type::Binary => "Binary", + Type::LowerExp => "LowerExp", + Type::UpperExp => "UpperExp", + } + } +} + +/// Type alias for the [`FormatSpec::width`]. +type Width<'a> = Count<'a>; + +/// Output of the [`maybe_format`] parser. +type MaybeFormat<'a> = Option<Format<'a>>; + +/// Output of the [`identifier`] parser. +type Identifier<'a> = &'a str; + +/// Output of the [`parameter`] parser. +type Parameter<'a> = Argument<'a>; + +/// [`str`] left to parse. +/// +/// [`str`]: prim@str +type LeftToParse<'a> = &'a str; + +/// Parses a `format_string` as defined in the [grammar spec][1]. +/// +/// # Grammar +/// +/// [`format_string`]` := `[`text`]` [`[`maybe_format text`]`] *` +/// +/// # Example +/// +/// ```text +/// Hello +/// Hello, {}! +/// {:?} +/// Hello {people}! +/// {} {} +/// {:04} +/// {par:-^#.0$?} +/// ``` +/// +/// # Return value +/// +/// - [`Some`] in case of successful parse. +/// - [`None`] otherwise (not all characters are consumed by underlying +/// parsers). +/// +/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#syntax +pub(crate) fn format_string(input: &str) -> Option<FormatString<'_>> { + let (mut input, _) = optional_result(text)(input); + + let formats = iter::repeat(()) + .scan(&mut input, |input, _| { + let (curr, format) = + alt(&mut [&mut maybe_format, &mut map(text, |(i, _)| (i, None))])( + input, + )?; + **input = curr; + Some(format) + }) + .flatten() + .collect(); + + // Should consume all tokens for a successful parse. + input.is_empty().then_some(FormatString { formats }) +} + +/// Parses a `maybe_format` as defined in the [grammar spec][1]. +/// +/// # Grammar +/// +/// [`maybe_format`]` := '{' '{' | '}' '}' | `[`format`] +/// +/// # Example +/// +/// ```text +/// {{ +/// }} +/// {:04} +/// {:#?} +/// {par:-^#.0$?} +/// ``` +/// +/// [`format`]: fn@format +/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#syntax +fn maybe_format(input: &str) -> Option<(LeftToParse<'_>, MaybeFormat<'_>)> { + alt(&mut [ + &mut map(str("{{"), |i| (i, None)), + &mut map(str("}}"), |i| (i, None)), + &mut map(format, |(i, format)| (i, Some(format))), + ])(input) +} + +/// Parses a `format` as defined in the [grammar spec][1]. +/// +/// # Grammar +/// +/// [`format`]` := '{' [`[`argument`]`] [':' `[`format_spec`]`] '}'` +/// +/// # Example +/// +/// ```text +/// {par} +/// {:#?} +/// {par:-^#.0$?} +/// ``` +/// +/// [`format`]: fn@format +/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#syntax +fn format(input: &str) -> Option<(LeftToParse<'_>, Format<'_>)> { + let input = char('{')(input)?; + + let (input, arg) = optional_result(argument)(input); + + let (input, spec) = map_or_else( + char(':'), + |i| Some((i, None)), + map(format_spec, |(i, s)| (i, Some(s))), + )(input)?; + + let input = char('}')(input)?; + + Some((input, Format { arg, spec })) +} + +/// Parses an `argument` as defined in the [grammar spec][1]. +/// +/// # Grammar +/// +/// [`argument`]` := `[`integer`]` | `[`identifier`] +/// +/// # Example +/// +/// ```text +/// 0 +/// ident +/// Минск +/// ``` +/// +/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#syntax +fn argument(input: &str) -> Option<(LeftToParse<'_>, Argument)> { + alt(&mut [ + &mut map(identifier, |(i, ident)| (i, Argument::Identifier(ident))), + &mut map(integer, |(i, int)| (i, Argument::Integer(int))), + ])(input) +} + +/// Parses a `format_spec` as defined in the [grammar spec][1]. +/// +/// # Grammar +/// +/// [`format_spec`]` := [[fill]align][sign]['#']['0'][width]` +/// `['.' `[`precision`]`]`[`type`] +/// +/// # Example +/// +/// ```text +/// ^ +/// <^ +/// ->+#0width$.precision$x? +/// ``` +/// +/// [`type`]: type_ +/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#syntax +fn format_spec(input: &str) -> Option<(LeftToParse<'_>, FormatSpec<'_>)> { + let input = unwrap_or_else( + alt(&mut [ + &mut try_seq(&mut [&mut any_char, &mut one_of("<^>")]), + &mut one_of("<^>"), + ]), + identity, + )(input); + + let input = seq(&mut [ + &mut optional(one_of("+-")), + &mut optional(char('#')), + &mut optional(try_seq(&mut [ + &mut char('0'), + &mut lookahead(check_char(|c| !matches!(c, '$'))), + ])), + ])(input); + + let (input, width) = optional_result(count)(input); + + let (input, precision) = map_or_else( + char('.'), + |i| Some((i, None)), + map(precision, |(i, p)| (i, Some(p))), + )(input)?; + + let (input, ty) = type_(input)?; + + Some(( + input, + FormatSpec { + width, + precision, + ty, + }, + )) +} + +/// Parses a `precision` as defined in the [grammar spec][1]. +/// +/// # Grammar +/// +/// [`precision`]` := `[`count`]` | '*'` +/// +/// # Example +/// +/// ```text +/// 0 +/// 42$ +/// par$ +/// * +/// ``` +/// +/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#syntax +fn precision(input: &str) -> Option<(LeftToParse<'_>, Precision<'_>)> { + alt(&mut [ + &mut map(count, |(i, c)| (i, Precision::Count(c))), + &mut map(char('*'), |i| (i, Precision::Star)), + ])(input) +} + +/// Parses a `type` as defined in the [grammar spec][1]. +/// +/// # Grammar +/// +/// [`type`]` := '' | '?' | 'x?' | 'X?' | identifier` +/// +/// # Example +/// +/// All possible [`Type`]s. +/// +/// ```text +/// ? +/// x? +/// X? +/// o +/// x +/// X +/// p +/// b +/// e +/// E +/// ``` +/// +/// [`type`]: type_ +/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#syntax +fn type_(input: &str) -> Option<(&str, Type)> { + alt(&mut [ + &mut map(str("x?"), |i| (i, Type::LowerDebug)), + &mut map(str("X?"), |i| (i, Type::UpperDebug)), + &mut map(char('?'), |i| (i, Type::Debug)), + &mut map(char('o'), |i| (i, Type::Octal)), + &mut map(char('x'), |i| (i, Type::LowerHex)), + &mut map(char('X'), |i| (i, Type::UpperHex)), + &mut map(char('p'), |i| (i, Type::Pointer)), + &mut map(char('b'), |i| (i, Type::Binary)), + &mut map(char('e'), |i| (i, Type::LowerExp)), + &mut map(char('E'), |i| (i, Type::UpperExp)), + &mut map(lookahead(char('}')), |i| (i, Type::Display)), + ])(input) +} + +/// Parses a `count` as defined in the [grammar spec][1]. +/// +/// # Grammar +/// +/// [`count`]` := `[`parameter`]` | `[`integer`] +/// +/// # Example +/// +/// ```text +/// 0 +/// 42$ +/// par$ +/// ``` +/// +/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#syntax +fn count(input: &str) -> Option<(LeftToParse<'_>, Count<'_>)> { + alt(&mut [ + &mut map(parameter, |(i, p)| (i, Count::Parameter(p))), + &mut map(integer, |(i, int)| (i, Count::Integer(int))), + ])(input) +} + +/// Parses a `parameter` as defined in the [grammar spec][1]. +/// +/// # Grammar +/// +/// [`parameter`]` := `[`argument`]` '$'` +/// +/// # Example +/// +/// ```text +/// 42$ +/// par$ +/// ``` +/// +/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#syntax +fn parameter(input: &str) -> Option<(LeftToParse<'_>, Parameter<'_>)> { + and_then(argument, |(i, arg)| map(char('$'), |i| (i, arg))(i))(input) +} + +/// Parses an `identifier` as defined in the [grammar spec][1]. +/// +/// # Grammar +/// +/// `IDENTIFIER_OR_KEYWORD : XID_Start XID_Continue* | _ XID_Continue+` +/// +/// See [rust reference][2] for more info. +/// +/// # Example +/// +/// ```text +/// identifier +/// Минск +/// ``` +/// +/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#syntax +/// [2]: https://doc.rust-lang.org/reference/identifiers.html +fn identifier(input: &str) -> Option<(LeftToParse<'_>, Identifier<'_>)> { + map( + alt(&mut [ + &mut map( + check_char(XID::is_xid_start), + take_while0(check_char(XID::is_xid_continue)), + ), + &mut and_then(char('_'), take_while1(check_char(XID::is_xid_continue))), + ]), + |(i, _)| (i, &input[..(input.as_bytes().len() - i.as_bytes().len())]), + )(input) +} + +/// Parses an `integer` as defined in the [grammar spec][1]. +/// +/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#syntax +fn integer(input: &str) -> Option<(LeftToParse<'_>, usize)> { + and_then( + take_while1(check_char(|c| c.is_ascii_digit())), + |(i, int)| int.parse().ok().map(|int| (i, int)), + )(input) +} + +/// Parses a `text` as defined in the [grammar spec][1]. +/// +/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#syntax +fn text(input: &str) -> Option<(LeftToParse<'_>, &str)> { + take_until1(any_char, one_of("{}"))(input) +} + +type Parser<'p> = &'p mut dyn FnMut(&str) -> &str; + +/// Applies non-failing parsers in sequence. +fn seq<'p>(parsers: &'p mut [Parser<'p>]) -> impl FnMut(&str) -> LeftToParse<'_> + 'p { + move |input| parsers.iter_mut().fold(input, |i, p| (**p)(i)) +} + +type FallibleParser<'p> = &'p mut dyn FnMut(&str) -> Option<&str>; + +/// Tries to apply parsers in sequence. Returns [`None`] in case one of them +/// returned [`None`]. +fn try_seq<'p>( + parsers: &'p mut [FallibleParser<'p>], +) -> impl FnMut(&str) -> Option<LeftToParse<'_>> + 'p { + move |input| parsers.iter_mut().try_fold(input, |i, p| (**p)(i)) +} + +/// Returns first successful parser or [`None`] in case all of them returned +/// [`None`]. +fn alt<'p, 'i, T: 'i>( + parsers: &'p mut [&'p mut dyn FnMut(&'i str) -> Option<T>], +) -> impl FnMut(&'i str) -> Option<T> + 'p { + move |input| parsers.iter_mut().find_map(|p| (**p)(input)) +} + +/// Maps output of the parser in case it returned [`Some`]. +fn map<'i, I: 'i, O: 'i>( + mut parser: impl FnMut(&'i str) -> Option<I>, + mut f: impl FnMut(I) -> O, +) -> impl FnMut(&'i str) -> Option<O> { + move |input| parser(input).map(&mut f) +} + +/// Maps output of the parser in case it returned [`Some`] or uses `default`. +fn map_or_else<'i, I: 'i, O: 'i>( + mut parser: impl FnMut(&'i str) -> Option<I>, + mut default: impl FnMut(&'i str) -> O, + mut f: impl FnMut(I) -> O, +) -> impl FnMut(&'i str) -> O { + move |input| parser(input).map_or_else(|| default(input), &mut f) +} + +/// Returns the contained [`Some`] value or computes it from a closure. +fn unwrap_or_else<'i, O: 'i>( + mut parser: impl FnMut(&'i str) -> Option<O>, + mut f: impl FnMut(&'i str) -> O, +) -> impl FnMut(&'i str) -> O { + move |input| parser(input).unwrap_or_else(|| f(input)) +} + +/// Returns [`None`] if the parser returned is [`None`], otherwise calls `f` +/// with the wrapped value and returns the result. +fn and_then<'i, I: 'i, O: 'i>( + mut parser: impl FnMut(&'i str) -> Option<I>, + mut f: impl FnMut(I) -> Option<O>, +) -> impl FnMut(&'i str) -> Option<O> { + move |input| parser(input).and_then(&mut f) +} + +/// Checks whether `parser` is successful while not advancing the original +/// `input`. +fn lookahead( + mut parser: impl FnMut(&str) -> Option<&str>, +) -> impl FnMut(&str) -> Option<LeftToParse<'_>> { + move |input| map(&mut parser, |_| input)(input) +} + +/// Makes underlying `parser` optional by returning the original `input` in case +/// it returned [`None`]. +fn optional( + mut parser: impl FnMut(&str) -> Option<&str>, +) -> impl FnMut(&str) -> LeftToParse<'_> { + move |input: &str| parser(input).unwrap_or(input) +} + +/// Makes underlying `parser` optional by returning the original `input` and +/// [`None`] in case it returned [`None`]. +fn optional_result<'i, T: 'i>( + mut parser: impl FnMut(&'i str) -> Option<(&'i str, T)>, +) -> impl FnMut(&'i str) -> (LeftToParse<'i>, Option<T>) { + move |input: &str| { + map_or_else(&mut parser, |i| (i, None), |(i, c)| (i, Some(c)))(input) + } +} + +/// Parses while `parser` is successful. Never fails. +fn take_while0( + mut parser: impl FnMut(&str) -> Option<&str>, +) -> impl FnMut(&str) -> (LeftToParse<'_>, &str) { + move |input| { + let mut cur = input; + while let Some(step) = parser(cur) { + cur = step; + } + ( + cur, + &input[..(input.as_bytes().len() - cur.as_bytes().len())], + ) + } +} + +/// Parses while `parser` is successful. Returns [`None`] in case `parser` never +/// succeeded. +fn take_while1( + mut parser: impl FnMut(&str) -> Option<&str>, +) -> impl FnMut(&str) -> Option<(LeftToParse<'_>, &str)> { + move |input| { + let mut cur = parser(input)?; + while let Some(step) = parser(cur) { + cur = step; + } + Some(( + cur, + &input[..(input.as_bytes().len() - cur.as_bytes().len())], + )) + } +} + +/// Parses with `basic` while `until` returns [`None`]. Returns [`None`] in case +/// `until` succeeded initially or `basic` never succeeded. Doesn't consume +/// [`char`]s parsed by `until`. +/// +/// [`char`]: fn@char +fn take_until1( + mut basic: impl FnMut(&str) -> Option<&str>, + mut until: impl FnMut(&str) -> Option<&str>, +) -> impl FnMut(&str) -> Option<(LeftToParse<'_>, &str)> { + move |input: &str| { + if until(input).is_some() { + return None; + } + let mut cur = basic(input)?; + loop { + if until(cur).is_some() { + break; + } + let Some(b) = basic(cur) else { + break; + }; + cur = b; + } + + Some(( + cur, + &input[..(input.as_bytes().len() - cur.as_bytes().len())], + )) + } +} + +/// Checks whether `input` starts with `s`. +fn str(s: &str) -> impl FnMut(&str) -> Option<LeftToParse<'_>> + '_ { + move |input| input.starts_with(s).then(|| &input[s.as_bytes().len()..]) +} + +/// Checks whether `input` starts with `c`. +fn char(c: char) -> impl FnMut(&str) -> Option<LeftToParse<'_>> { + move |input| input.starts_with(c).then(|| &input[c.len_utf8()..]) +} + +/// Checks whether first [`char`] suits `check`. +/// +/// [`char`]: fn@char +fn check_char( + mut check: impl FnMut(char) -> bool, +) -> impl FnMut(&str) -> Option<LeftToParse<'_>> { + move |input| { + input + .chars() + .next() + .and_then(|c| check(c).then(|| &input[c.len_utf8()..])) + } +} + +/// Checks whether first [`char`] of input is present in `chars`. +/// +/// [`char`]: fn@char +fn one_of(chars: &str) -> impl FnMut(&str) -> Option<LeftToParse<'_>> + '_ { + move |input: &str| chars.chars().find_map(|c| char(c)(input)) +} + +/// Parses any [`char`]. +/// +/// [`char`]: fn@char +fn any_char(input: &str) -> Option<LeftToParse<'_>> { + input.chars().next().map(|c| &input[c.len_utf8()..]) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn text() { + assert_eq!(format_string(""), Some(FormatString { formats: vec![] })); + assert_eq!( + format_string("test"), + Some(FormatString { formats: vec![] }), + ); + assert_eq!( + format_string("Минск"), + Some(FormatString { formats: vec![] }), + ); + assert_eq!(format_string("🦀"), Some(FormatString { formats: vec![] })); + } + + #[test] + fn argument() { + assert_eq!( + format_string("{}"), + Some(FormatString { + formats: vec![Format { + arg: None, + spec: None, + }], + }), + ); + assert_eq!( + format_string("{0}"), + Some(FormatString { + formats: vec![Format { + arg: Some(Argument::Integer(0)), + spec: None, + }], + }), + ); + assert_eq!( + format_string("{par}"), + Some(FormatString { + formats: vec![Format { + arg: Some(Argument::Identifier("par")), + spec: None, + }], + }), + ); + assert_eq!( + format_string("{Минск}"), + Some(FormatString { + formats: vec![Format { + arg: Some(Argument::Identifier("Минск")), + spec: None, + }], + }), + ); + } + + #[test] + fn spec() { + assert_eq!( + format_string("{:}"), + Some(FormatString { + formats: vec![Format { + arg: None, + spec: Some(FormatSpec { + width: None, + precision: None, + ty: Type::Display, + }), + }], + }), + ); + assert_eq!( + format_string("{:^}"), + Some(FormatString { + formats: vec![Format { + arg: None, + spec: Some(FormatSpec { + width: None, + precision: None, + ty: Type::Display, + }), + }], + }), + ); + assert_eq!( + format_string("{:-<}"), + Some(FormatString { + formats: vec![Format { + arg: None, + spec: Some(FormatSpec { + width: None, + precision: None, + ty: Type::Display, + }), + }], + }), + ); + assert_eq!( + format_string("{: <}"), + Some(FormatString { + formats: vec![Format { + arg: None, + spec: Some(FormatSpec { + width: None, + precision: None, + ty: Type::Display, + }), + }], + }), + ); + assert_eq!( + format_string("{:^<}"), + Some(FormatString { + formats: vec![Format { + arg: None, + spec: Some(FormatSpec { + width: None, + precision: None, + ty: Type::Display, + }), + }], + }), + ); + assert_eq!( + format_string("{:+}"), + Some(FormatString { + formats: vec![Format { + arg: None, + spec: Some(FormatSpec { + width: None, + precision: None, + ty: Type::Display, + }), + }], + }), + ); + assert_eq!( + format_string("{:^<-}"), + Some(FormatString { + formats: vec![Format { + arg: None, + spec: Some(FormatSpec { + width: None, + precision: None, + ty: Type::Display, + }), + }], + }), + ); + assert_eq!( + format_string("{:#}"), + Some(FormatString { + formats: vec![Format { + arg: None, + spec: Some(FormatSpec { + width: None, + precision: None, + ty: Type::Display, + }), + }], + }), + ); + assert_eq!( + format_string("{:+#}"), + Some(FormatString { + formats: vec![Format { + arg: None, + spec: Some(FormatSpec { + width: None, + precision: None, + ty: Type::Display, + }), + }], + }), + ); + assert_eq!( + format_string("{:-<#}"), + Some(FormatString { + formats: vec![Format { + arg: None, + spec: Some(FormatSpec { + width: None, + precision: None, + ty: Type::Display, + }), + }], + }), + ); + assert_eq!( + format_string("{:^<-#}"), + Some(FormatString { + formats: vec![Format { + arg: None, + spec: Some(FormatSpec { + width: None, + precision: None, + ty: Type::Display, + }), + }], + }), + ); + assert_eq!( + format_string("{:0}"), + Some(FormatString { + formats: vec![Format { + arg: None, + spec: Some(FormatSpec { + width: None, + precision: None, + ty: Type::Display, + }), + }], + }), + ); + assert_eq!( + format_string("{:#0}"), + Some(FormatString { + formats: vec![Format { + arg: None, + spec: Some(FormatSpec { + width: None, + precision: None, + ty: Type::Display, + }), + }], + }), + ); + assert_eq!( + format_string("{:-0}"), + Some(FormatString { + formats: vec![Format { + arg: None, + spec: Some(FormatSpec { + width: None, + precision: None, + ty: Type::Display, + }), + }], + }), + ); + assert_eq!( + format_string("{:^<0}"), + Some(FormatString { + formats: vec![Format { + arg: None, + spec: Some(FormatSpec { + width: None, + precision: None, + ty: Type::Display, + }), + }], + }), + ); + assert_eq!( + format_string("{:^<+#0}"), + Some(FormatString { + formats: vec![Format { + arg: None, + spec: Some(FormatSpec { + width: None, + precision: None, + ty: Type::Display, + }), + }], + }), + ); + assert_eq!( + format_string("{:1}"), + Some(FormatString { + formats: vec![Format { + arg: None, + spec: Some(FormatSpec { + width: Some(Count::Integer(1)), + precision: None, + ty: Type::Display, + }), + }], + }), + ); + assert_eq!( + format_string("{:1$}"), + Some(FormatString { + formats: vec![Format { + arg: None, + spec: Some(FormatSpec { + width: Some(Count::Parameter(Argument::Integer(1))), + precision: None, + ty: Type::Display, + }), + }], + }), + ); + assert_eq!( + format_string("{:par$}"), + Some(FormatString { + formats: vec![Format { + arg: None, + spec: Some(FormatSpec { + width: Some(Count::Parameter(Argument::Identifier("par"))), + precision: None, + ty: Type::Display, + }), + }], + }), + ); + assert_eq!( + format_string("{:-^-#0Минск$}"), + Some(FormatString { + formats: vec![Format { + arg: None, + spec: Some(FormatSpec { + width: Some(Count::Parameter(Argument::Identifier("Минск"))), + precision: None, + ty: Type::Display, + }), + }], + }), + ); + assert_eq!( + format_string("{:.*}"), + Some(FormatString { + formats: vec![Format { + arg: None, + spec: Some(FormatSpec { + width: None, + precision: Some(Precision::Star), + ty: Type::Display, + }), + }], + }), + ); + assert_eq!( + format_string("{:.0}"), + Some(FormatString { + formats: vec![Format { + arg: None, + spec: Some(FormatSpec { + width: None, + precision: Some(Precision::Count(Count::Integer(0))), + ty: Type::Display, + }), + }], + }), + ); + assert_eq!( + format_string("{:.0$}"), + Some(FormatString { + formats: vec![Format { + arg: None, + spec: Some(FormatSpec { + width: None, + precision: Some(Precision::Count(Count::Parameter( + Argument::Integer(0), + ))), + ty: Type::Display, + }), + }], + }), + ); + assert_eq!( + format_string("{:.par$}"), + Some(FormatString { + formats: vec![Format { + arg: None, + spec: Some(FormatSpec { + width: None, + precision: Some(Precision::Count(Count::Parameter( + Argument::Identifier("par"), + ))), + ty: Type::Display, + }), + }], + }), + ); + assert_eq!( + format_string("{: >+#2$.par$}"), + Some(FormatString { + formats: vec![Format { + arg: None, + spec: Some(FormatSpec { + width: Some(Count::Parameter(Argument::Integer(2))), + precision: Some(Precision::Count(Count::Parameter( + Argument::Identifier("par"), + ))), + ty: Type::Display, + }), + }], + }), + ); + assert_eq!( + format_string("{:x?}"), + Some(FormatString { + formats: vec![Format { + arg: None, + spec: Some(FormatSpec { + width: None, + precision: None, + ty: Type::LowerDebug, + }), + }], + }), + ); + assert_eq!( + format_string("{:E}"), + Some(FormatString { + formats: vec![Format { + arg: None, + spec: Some(FormatSpec { + width: None, + precision: None, + ty: Type::UpperExp, + }), + }], + }), + ); + assert_eq!( + format_string("{: >+#par$.par$X?}"), + Some(FormatString { + formats: vec![Format { + arg: None, + spec: Some(FormatSpec { + width: Some(Count::Parameter(Argument::Identifier("par"))), + precision: Some(Precision::Count(Count::Parameter( + Argument::Identifier("par"), + ))), + ty: Type::UpperDebug, + }), + }], + }), + ); + } + + #[test] + fn full() { + assert_eq!( + format_string("prefix{{{0:#?}postfix{par:-^par$.a$}}}"), + Some(FormatString { + formats: vec![ + Format { + arg: Some(Argument::Integer(0)), + spec: Some(FormatSpec { + width: None, + precision: None, + ty: Type::Debug, + }), + }, + Format { + arg: Some(Argument::Identifier("par")), + spec: Some(FormatSpec { + width: Some(Count::Parameter(Argument::Identifier("par"))), + precision: Some(Precision::Count(Count::Parameter( + Argument::Identifier("a"), + ))), + ty: Type::Display, + }), + }, + ], + }), + ); + } + + #[test] + fn error() { + assert_eq!(format_string("{"), None); + assert_eq!(format_string("}"), None); + assert_eq!(format_string("{{}"), None); + assert_eq!(format_string("{:x?"), None); + assert_eq!(format_string("{:.}"), None); + assert_eq!(format_string("{:q}"), None); + assert_eq!(format_string("{:par}"), None); + assert_eq!(format_string("{⚙️}"), None); + } +} |