pw_format: Refactor format string parsing for better core::fmt support

Change-Id: I8e42720f487a022c9dd5a2ca997a72c6b349a742
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/208656
Presubmit-Verified: CQ Bot Account <pigweed-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Taylor Cramer <cramertj@google.com>
Lint: Lint 🤖 <android-build-ayeaye@system.gserviceaccount.com>
Pigweed-Auto-Submit: Erik Gilling <konkers@google.com>
Commit-Queue: Auto-Submit <auto-submit@pigweed-service-accounts.iam.gserviceaccount.com>
diff --git a/pw_format/rust/pw_format/core_fmt.rs b/pw_format/rust/pw_format/core_fmt.rs
index 86500ab..6e74e95 100644
--- a/pw_format/rust/pw_format/core_fmt.rs
+++ b/pw_format/rust/pw_format/core_fmt.rs
@@ -29,7 +29,7 @@
 
 use crate::{
     fixed_width, precision, Alignment, Argument, ConversionSpec, Flag, FormatFragment,
-    FormatString, MinFieldWidth, Precision, Specifier,
+    FormatString, MinFieldWidth, Precision, Primitive, Style,
 };
 
 /// The `name` in a `{name}` format string.  Matches a Rust identifier.
@@ -66,26 +66,26 @@
 /// An explicit formatting type
 ///
 /// i.e. the `x?` in `{:x?}
-fn explicit_type(input: &str) -> IResult<&str, Specifier> {
+fn explicit_type(input: &str) -> IResult<&str, Style> {
     alt((
-        value(Specifier::Debug, tag("?")),
-        value(Specifier::HexDebug, tag("x?")),
-        value(Specifier::UpperHexDebug, tag("X?")),
-        value(Specifier::Octal, tag("o")),
-        value(Specifier::Hex, tag("x")),
-        value(Specifier::UpperHex, tag("X")),
-        value(Specifier::Pointer, tag("p")),
-        value(Specifier::Binary, tag("b")),
-        value(Specifier::Exponential, tag("e")),
-        value(Specifier::UpperExponential, tag("E")),
+        value(Style::Debug, tag("?")),
+        value(Style::HexDebug, tag("x?")),
+        value(Style::UpperHexDebug, tag("X?")),
+        value(Style::Octal, tag("o")),
+        value(Style::Hex, tag("x")),
+        value(Style::UpperHex, tag("X")),
+        value(Style::Pointer, tag("p")),
+        value(Style::Binary, tag("b")),
+        value(Style::Exponential, tag("e")),
+        value(Style::UpperExponential, tag("E")),
     ))(input)
 }
 
 /// An optional explicit formatting type
 ///
 /// i.e. the `x?` in `{:x?} or no type as in `{:}`
-fn ty(input: &str) -> IResult<&str, Specifier> {
-    let (input, spec) = explicit_type(input).unwrap_or_else(|_| (input, Specifier::Untyped));
+fn style(input: &str) -> IResult<&str, Style> {
+    let (input, spec) = explicit_type(input).unwrap_or_else(|_| (input, Style::None));
 
     Ok((input, spec))
 }
@@ -156,7 +156,7 @@
     let (input, flags) = flags(input)?;
     let (input, width) = opt(fixed_width)(input)?;
     let (input, precision) = precision(input)?;
-    let (input, specifier) = ty(input)?;
+    let (input, style) = style(input)?;
 
     Ok((
         input,
@@ -168,7 +168,8 @@
             min_field_width: width.unwrap_or(MinFieldWidth::None),
             precision,
             length: None,
-            specifier,
+            primitive: Primitive::Untyped, // All core::fmt primitives are untyped.
+            style,
         },
     ))
 }
@@ -192,7 +193,8 @@
         min_field_width: MinFieldWidth::None,
         precision: Precision::None,
         length: None,
-        specifier: Specifier::Untyped,
+        primitive: Primitive::Untyped,
+        style: Style::None,
     });
 
     spec.argument = argument;
@@ -238,11 +240,11 @@
 
     #[test]
     fn type_parses_correctly() {
-        assert_eq!(ty(""), Ok(("", Specifier::Untyped)));
-        assert_eq!(ty("?"), Ok(("", Specifier::Debug)));
-        assert_eq!(ty("x?"), Ok(("", Specifier::HexDebug)));
-        assert_eq!(ty("X?"), Ok(("", Specifier::UpperHexDebug)));
-        assert_eq!(ty("o"), Ok(("", Specifier::Octal)));
+        assert_eq!(style(""), Ok(("", Style::None)));
+        assert_eq!(style("?"), Ok(("", Style::Debug)));
+        assert_eq!(style("x?"), Ok(("", Style::HexDebug)));
+        assert_eq!(style("X?"), Ok(("", Style::UpperHexDebug)));
+        assert_eq!(style("o"), Ok(("", Style::Octal)));
     }
 
     #[test]
@@ -316,7 +318,8 @@
                     min_field_width: MinFieldWidth::None,
                     precision: Precision::None,
                     length: None,
-                    specifier: Specifier::Untyped,
+                    primitive: Primitive::Untyped,
+                    style: Style::None,
                 }
             ))
         );
diff --git a/pw_format/rust/pw_format/lib.rs b/pw_format/rust/pw_format/lib.rs
index fb0c193..84673e2 100644
--- a/pw_format/rust/pw_format/lib.rs
+++ b/pw_format/rust/pw_format/lib.rs
@@ -31,11 +31,11 @@
 //! ```
 //! use pw_format::{
 //!     Alignment, Argument, ConversionSpec, Flag, FormatFragment, FormatString,
-//!     Length, Precision, Specifier, MinFieldWidth,
+//!     Length, MinFieldWidth, Precision, Primitive, Style,
 //! };
 //!
 //! let format_string =
-//!   FormatString::parse_printf("long double %+ 4.2Lg is %-03hd%%.").unwrap();
+//!   FormatString::parse_printf("long double %+ 4.2Lf is %-03hd%%.").unwrap();
 //!
 //! assert_eq!(format_string, FormatString {
 //!   fragments: vec![
@@ -48,7 +48,8 @@
 //!           min_field_width: MinFieldWidth::Fixed(4),
 //!           precision: Precision::Fixed(2),
 //!           length: Some(Length::LongDouble),
-//!           specifier: Specifier::SmallDouble
+//!           primitive: Primitive::Float,
+//!           style: Style::None,
 //!       }),
 //!       FormatFragment::Literal(" is ".to_string()),
 //!       FormatFragment::Conversion(ConversionSpec {
@@ -61,7 +62,8 @@
 //!           min_field_width: MinFieldWidth::Fixed(3),
 //!           precision: Precision::None,
 //!           length: Some(Length::Short),
-//!           specifier: Specifier::Decimal
+//!           primitive: Primitive::Integer,
+//!           style: Style::None,
 //!       }),
 //!       FormatFragment::Literal("%.".to_string()),
 //!   ]
@@ -79,62 +81,61 @@
     combinator::{map, map_res},
     IResult,
 };
+use quote::{quote, ToTokens};
 
 pub mod macros;
 
 mod core_fmt;
 mod printf;
 
-#[derive(Clone, Debug, PartialEq, Eq)]
-/// A printf specifier (the 'd' in %d).
-pub enum Specifier {
-    /// `%d`
-    Decimal,
-
-    /// `%i`
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+/// Primitive type of a conversion (integer, float, string, etc.)
+pub enum Primitive {
+    /// Signed integer primitive.
     Integer,
 
-    /// `%o`
-    Octal,
-
-    /// `%u`
+    /// Unsigned integer primitive.
     Unsigned,
 
-    /// `%x`
-    Hex,
+    /// Floating point primitive.
+    Float,
 
-    /// `%X`
-    UpperHex,
-
-    /// `%f`
-    Double,
-
-    /// `%F`
-    UpperDouble,
-
-    /// `%e`
-    Exponential,
-
-    /// `%E`
-    UpperExponential,
-
-    /// `%g`
-    SmallDouble,
-
-    /// `%G`
-    UpperSmallDouble,
-
-    /// `%c`
-    Char,
-
-    /// `%s`
+    /// String primitive.
     String,
 
-    /// `%p`
+    /// Character primitive.
+    Character,
+
+    /// Pointer primitive.
     Pointer,
 
-    /// `%v`
+    /// Untyped primitive.
     Untyped,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+/// The abstract formatting style for a conversion.
+pub enum Style {
+    /// No style specified, use defaults.
+    None,
+
+    /// Octal rendering (i.e. "%o" or "{:o}").
+    Octal,
+
+    /// Hexadecimal rendering (i.e. "%x" or "{:x}").
+    Hex,
+
+    /// Upper case hexadecimal rendering (i.e. "%X" or "{:X}").
+    UpperHex,
+
+    /// Exponential rendering (i.e. "%e" or "{:e}".
+    Exponential,
+
+    /// Upper case exponential rendering (i.e. "%E" or "{:E}".
+    UpperExponential,
+
+    /// Pointer type rendering (i.e. "%p" or "{:p}").
+    Pointer,
 
     /// `core::fmt`'s `{:?}`
     Debug,
@@ -145,10 +146,33 @@
     /// `core::fmt`'s `{:X?}`
     UpperHexDebug,
 
-    /// `core::fmt`'s `{:b}`
+    /// Unsupported binary rendering
+    ///
+    /// This variant exists so that the proc macros can give useful error
+    /// messages.
     Binary,
 }
 
+/// Implemented for testing through the pw_format_test_macros crate.
+impl ToTokens for Style {
+    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
+        let new_tokens = match self {
+            Style::None => quote!(pw_format::Style::None),
+            Style::Octal => quote!(pw_format::Style::Octal),
+            Style::Hex => quote!(pw_format::Style::Hex),
+            Style::UpperHex => quote!(pw_format::Style::UpperHex),
+            Style::Exponential => quote!(pw_format::Style::Exponential),
+            Style::UpperExponential => quote!(pw_format::Style::UpperExponential),
+            Style::Debug => quote!(pw_format::Style::Debug),
+            Style::HexDebug => quote!(pw_format::Style::HexDebug),
+            Style::UpperHexDebug => quote!(pw_format::Style::UpperHexDebug),
+            Style::Pointer => quote!(pw_format::Style::Pointer),
+            Style::Binary => quote!(pw_format::Style::Binary),
+        };
+        new_tokens.to_tokens(tokens);
+    }
+}
+
 #[derive(Clone, Debug, Hash, PartialEq, Eq)]
 /// A printf flag (the '+' in %+d).
 pub enum Flag {
@@ -273,8 +297,10 @@
     pub precision: Precision,
     /// ConversionSpec's [Length] argument.
     pub length: Option<Length>,
-    /// ConversionSpec's [Specifier].
-    pub specifier: Specifier,
+    /// ConversionSpec's [Primitive].
+    pub primitive: Primitive,
+    /// ConversionSpec's [Style].
+    pub style: Style,
 }
 
 #[derive(Clone, Debug, PartialEq, Eq)]
diff --git a/pw_format/rust/pw_format/macros.rs b/pw_format/rust/pw_format/macros.rs
index e01581a..ac711eb 100644
--- a/pw_format/rust/pw_format/macros.rs
+++ b/pw_format/rust/pw_format/macros.rs
@@ -46,7 +46,8 @@
 };
 
 use crate::{
-    ConversionSpec, FormatFragment, FormatString, Length, MinFieldWidth, Precision, Specifier,
+    ConversionSpec, FormatFragment, FormatString, Length, MinFieldWidth, Precision, Primitive,
+    Style,
 };
 
 mod keywords {
@@ -77,57 +78,6 @@
 /// An alias for a Result with an ``Error``
 pub type Result<T> = core::result::Result<T, Error>;
 
-/// Style in which to display the an integer.
-///
-/// In order to maintain compatibility with `printf` style systems, sign
-/// and base are combined.
-#[derive(Clone, Copy, Debug, PartialEq)]
-pub enum IntegerDisplayType {
-    /// Signed integer
-    Signed,
-    /// Unsigned integer
-    Unsigned,
-    /// Unsigned octal
-    Octal,
-    /// Unsigned hex with lower case letters
-    Hex,
-    /// Unsigned hex with upper case letters
-    UpperHex,
-}
-
-impl TryFrom<crate::Specifier> for IntegerDisplayType {
-    type Error = Error;
-
-    fn try_from(value: Specifier) -> Result<Self> {
-        match value {
-            Specifier::Decimal | Specifier::Integer => Ok(Self::Signed),
-            Specifier::Unsigned => Ok(Self::Unsigned),
-            Specifier::Octal => Ok(Self::Octal),
-            Specifier::Hex => Ok(Self::Hex),
-            Specifier::UpperHex => Ok(Self::UpperHex),
-            _ => Err(Error::new("No valid IntegerDisplayType for {value:?}.")),
-        }
-    }
-}
-
-/// Implemented for testing through the pw_format_test_macros crate.
-impl ToTokens for IntegerDisplayType {
-    fn to_tokens(&self, tokens: &mut TokenStream2) {
-        let new_tokens = match self {
-            IntegerDisplayType::Signed => quote!(pw_format::macros::IntegerDisplayType::Signed),
-            IntegerDisplayType::Unsigned => {
-                quote!(pw_format::macros::IntegerDisplayType::Unsigned)
-            }
-            IntegerDisplayType::Octal => quote!(pw_format::macros::IntegerDisplayType::Octal),
-            IntegerDisplayType::Hex => quote!(pw_format::macros::IntegerDisplayType::Hex),
-            IntegerDisplayType::UpperHex => {
-                quote!(pw_format::macros::IntegerDisplayType::UpperHex)
-            }
-        };
-        new_tokens.to_tokens(tokens);
-    }
-}
-
 /// A code generator for implementing a `pw_format` style macro.
 ///
 /// This trait serves as the primary interface between `pw_format` and a
@@ -159,7 +109,8 @@
     /// Process an integer conversion.
     fn integer_conversion(
         &mut self,
-        display: IntegerDisplayType,
+        style: Style,
+        signed: bool,
         type_width: u8, // This should probably be an enum
         expression: Arg,
     ) -> Result<()>;
@@ -343,13 +294,8 @@
     spec: &ConversionSpec,
     args: &mut VecDeque<Arg>,
 ) -> Result<()> {
-    match spec.specifier {
-        Specifier::Decimal
-        | Specifier::Integer
-        | Specifier::Octal
-        | Specifier::Unsigned
-        | Specifier::Hex
-        | Specifier::UpperHex => {
+    match spec.primitive {
+        Primitive::Integer | Primitive::Unsigned => {
             // TODO: b/281862660 - Support Width::Variable and Precision::Variable.
             if spec.min_field_width == MinFieldWidth::Variable {
                 return Err(Error::new(
@@ -363,6 +309,10 @@
                 ));
             }
 
+            if spec.style == Style::Binary {
+                return Err(Error::new("Binary output style is not supported."));
+            }
+
             let arg = next_arg(spec, args)?;
             let bits = match spec.length.unwrap_or(Length::Long) {
                 Length::Char => 8,
@@ -379,13 +329,14 @@
                 }
             };
 
-            let display: IntegerDisplayType =
-                spec.specifier.clone().try_into().expect(
-                    "Specifier is guaranteed to convert display type but enclosing match arm.",
-                );
-            generator.integer_conversion(display, bits, arg)
+            generator.integer_conversion(
+                spec.style,
+                spec.primitive == Primitive::Integer,
+                bits,
+                arg,
+            )
         }
-        Specifier::String => {
+        Primitive::String => {
             // TODO: b/281862660 - Support Width::Variable and Precision::Variable.
             if spec.min_field_width == MinFieldWidth::Variable {
                 return Err(Error::new(
@@ -402,32 +353,23 @@
             let arg = next_arg(spec, args)?;
             generator.string_conversion(arg)
         }
-        Specifier::Char => {
+        Primitive::Character => {
             let arg = next_arg(spec, args)?;
             generator.char_conversion(arg)
         }
 
-        Specifier::Untyped => {
+        Primitive::Untyped => {
             let arg = next_arg(spec, args)?;
             generator.untyped_conversion(arg)
         }
 
-        Specifier::Double
-        | Specifier::UpperDouble
-        | Specifier::Exponential
-        | Specifier::UpperExponential
-        | Specifier::SmallDouble
-        | Specifier::UpperSmallDouble => {
+        Primitive::Float => {
             // TODO: b/281862328 - Support floating point numbers.
             Err(Error::new("Floating point numbers are not supported."))
         }
 
         // TODO: b/281862333 - Support pointers.
-        Specifier::Pointer => Err(Error::new("Pointer types are not supported.")),
-        Specifier::Debug => todo!(),
-        Specifier::HexDebug => todo!(),
-        Specifier::UpperHexDebug => todo!(),
-        Specifier::Binary => todo!(),
+        Primitive::Pointer => Err(Error::new("Pointer types are not supported.")),
     }
 }
 
@@ -646,7 +588,8 @@
 
     fn integer_conversion(
         &mut self,
-        display: IntegerDisplayType,
+        style: Style,
+        signed: bool,
         type_width: u8, // in bits
         expression: Arg,
     ) -> Result<()> {
@@ -663,12 +606,22 @@
             }
         };
 
-        let (conversion, ty) = match display {
-            IntegerDisplayType::Signed => ("d", format_ident!("i{type_width}")),
-            IntegerDisplayType::Unsigned => ("u", format_ident!("u{type_width}")),
-            IntegerDisplayType::Octal => ("o", format_ident!("u{type_width}")),
-            IntegerDisplayType::Hex => ("x", format_ident!("u{type_width}")),
-            IntegerDisplayType::UpperHex => ("X", format_ident!("u{type_width}")),
+        let (conversion, ty) = match style {
+            Style::None => {
+                if signed {
+                    ("d", format_ident!("i{type_width}"))
+                } else {
+                    ("u", format_ident!("u{type_width}"))
+                }
+            }
+            Style::Octal => ("o", format_ident!("u{type_width}")),
+            Style::Hex => ("x", format_ident!("u{type_width}")),
+            Style::UpperHex => ("X", format_ident!("u{type_width}")),
+            _ => {
+                return Err(Error::new(&format!(
+                    "printf backend does not support formatting integers with {style:?} style",
+                )))
+            }
         };
 
         match self.inner.integer_conversion(ty, expression)? {
@@ -786,16 +739,27 @@
 
     fn integer_conversion(
         &mut self,
-        display: IntegerDisplayType,
+        style: Style,
+        signed: bool,
         type_width: u8, // in bits
         expression: Arg,
     ) -> Result<()> {
-        let (conversion, ty) = match display {
-            IntegerDisplayType::Signed => ("{}", format_ident!("i{type_width}")),
-            IntegerDisplayType::Unsigned => ("{}", format_ident!("u{type_width}")),
-            IntegerDisplayType::Octal => ("{:o}", format_ident!("u{type_width}")),
-            IntegerDisplayType::Hex => ("{:x}", format_ident!("u{type_width}")),
-            IntegerDisplayType::UpperHex => ("{:X}", format_ident!("u{type_width}")),
+        let ty = if signed {
+            format_ident!("i{type_width}")
+        } else {
+            format_ident!("u{type_width}")
+        };
+
+        let conversion = match style {
+            Style::None => "{}",
+            Style::Octal => "{:o}",
+            Style::Hex => "{:x}",
+            Style::UpperHex => "{:X}",
+            _ => {
+                return Err(Error::new(&format!(
+                    "core::fmt backend does not support formatting integers with {style:?} style",
+                )))
+            }
         };
 
         match self.inner.integer_conversion(ty, expression)? {
diff --git a/pw_format/rust/pw_format/printf.rs b/pw_format/rust/pw_format/printf.rs
index c7d79f5..d15fd4c 100644
--- a/pw_format/rust/pw_format/printf.rs
+++ b/pw_format/rust/pw_format/printf.rs
@@ -26,32 +26,31 @@
 
 use crate::{
     precision, width, Alignment, Argument, ConversionSpec, Flag, FormatFragment, FormatString,
-    Length, Specifier,
+    Length, Primitive, Style,
 };
 
-fn map_specifier(value: char) -> Result<Specifier, String> {
+fn map_specifier(value: char) -> Result<(Primitive, Style), String> {
     match value {
-        'd' => Ok(Specifier::Decimal),
-        'i' => Ok(Specifier::Integer),
-        'o' => Ok(Specifier::Octal),
-        'u' => Ok(Specifier::Unsigned),
-        'x' => Ok(Specifier::Hex),
-        'X' => Ok(Specifier::UpperHex),
-        'f' => Ok(Specifier::Double),
-        'F' => Ok(Specifier::UpperDouble),
-        'e' => Ok(Specifier::Exponential),
-        'E' => Ok(Specifier::UpperExponential),
-        'g' => Ok(Specifier::SmallDouble),
-        'G' => Ok(Specifier::UpperSmallDouble),
-        'c' => Ok(Specifier::Char),
-        's' => Ok(Specifier::String),
-        'p' => Ok(Specifier::Pointer),
-        'v' => Ok(Specifier::Untyped),
+        'd' | 'i' => Ok((Primitive::Integer, Style::None)),
+        'o' => Ok((Primitive::Unsigned, Style::Octal)),
+        'u' => Ok((Primitive::Unsigned, Style::None)),
+        'x' => Ok((Primitive::Unsigned, Style::Hex)),
+        'X' => Ok((Primitive::Unsigned, Style::UpperHex)),
+        'f' => Ok((Primitive::Float, Style::None)),
+        'e' => Ok((Primitive::Float, Style::Exponential)),
+        'E' => Ok((Primitive::Float, Style::UpperExponential)),
+        'c' => Ok((Primitive::Character, Style::None)),
+        's' => Ok((Primitive::String, Style::None)),
+        'p' => Ok((Primitive::Pointer, Style::Pointer)),
+        'v' => Ok((Primitive::Untyped, Style::None)),
+        'F' => Err("%F is not supported because it does not have a core::fmt analog".to_string()),
+        'g' => Err("%g is not supported because it does not have a core::fmt analog".to_string()),
+        'G' => Err("%G is not supported because it does not have a core::fmt analog".to_string()),
         _ => Err(format!("Unsupported format specifier '{}'", value)),
     }
 }
 
-fn specifier(input: &str) -> IResult<&str, Specifier> {
+fn specifier(input: &str) -> IResult<&str, (Primitive, Style)> {
     map_res(anychar, map_specifier)(input)
 }
 
@@ -92,7 +91,7 @@
     let (input, width) = width(input)?;
     let (input, precision) = precision(input)?;
     let (input, length) = length(input)?;
-    let (input, specifier) = specifier(input)?;
+    let (input, (primitive, style)) = specifier(input)?;
 
     Ok((
         input,
@@ -108,7 +107,8 @@
             min_field_width: width,
             precision,
             length,
-            specifier,
+            primitive,
+            style,
         },
     ))
 }
diff --git a/pw_format/rust/pw_format/tests/core_fmt.rs b/pw_format/rust/pw_format/tests/core_fmt.rs
index 1eb8f43..effd632 100644
--- a/pw_format/rust/pw_format/tests/core_fmt.rs
+++ b/pw_format/rust/pw_format/tests/core_fmt.rs
@@ -30,7 +30,8 @@
                     min_field_width: MinFieldWidth::None,
                     precision: Precision::None,
                     length: None,
-                    specifier: Specifier::Debug,
+                    primitive: Primitive::Untyped,
+                    style: Style::Debug,
                 }),
                 FormatFragment::Literal("!".to_string()),
             ]
@@ -48,7 +49,8 @@
                 min_field_width: MinFieldWidth::Fixed(4),
                 precision: Precision::None,
                 length: None,
-                specifier: Specifier::Untyped,
+                primitive: Primitive::Untyped,
+                style: Style::None,
             })]
         })
     );
@@ -64,7 +66,8 @@
                 min_field_width: MinFieldWidth::None,
                 precision: Precision::None,
                 length: None,
-                specifier: Specifier::Debug,
+                primitive: Primitive::Untyped,
+                style: Style::Debug,
             })]
         })
     );
@@ -80,7 +83,8 @@
                 min_field_width: MinFieldWidth::None,
                 precision: Precision::None,
                 length: None,
-                specifier: Specifier::Untyped,
+                primitive: Primitive::Untyped,
+                style: Style::None,
             })]
         })
     );
@@ -96,7 +100,8 @@
                 min_field_width: MinFieldWidth::None,
                 precision: Precision::None,
                 length: None,
-                specifier: Specifier::Untyped,
+                primitive: Primitive::Untyped,
+                style: Style::None,
             })]
         })
     );
@@ -112,7 +117,8 @@
                 min_field_width: MinFieldWidth::None,
                 precision: Precision::None,
                 length: None,
-                specifier: Specifier::Untyped,
+                primitive: Primitive::Untyped,
+                style: Style::None,
             })]
         })
     );
@@ -128,7 +134,8 @@
                 min_field_width: MinFieldWidth::Fixed(5),
                 precision: Precision::None,
                 length: None,
-                specifier: Specifier::Untyped,
+                primitive: Primitive::Untyped,
+                style: Style::None,
             })]
         })
     );
@@ -144,7 +151,8 @@
                 min_field_width: MinFieldWidth::Fixed(5),
                 precision: Precision::None,
                 length: None,
-                specifier: Specifier::Untyped,
+                primitive: Primitive::Untyped,
+                style: Style::None,
             })]
         })
     );
@@ -160,7 +168,8 @@
                 min_field_width: MinFieldWidth::Fixed(5),
                 precision: Precision::None,
                 length: None,
-                specifier: Specifier::Untyped,
+                primitive: Primitive::Untyped,
+                style: Style::None,
             })]
         })
     );
@@ -176,7 +185,8 @@
                 min_field_width: MinFieldWidth::Fixed(5),
                 precision: Precision::None,
                 length: None,
-                specifier: Specifier::Untyped,
+                primitive: Primitive::Untyped,
+                style: Style::None,
             })]
         })
     );
@@ -192,7 +202,8 @@
                 min_field_width: MinFieldWidth::None,
                 precision: Precision::None,
                 length: None,
-                specifier: Specifier::Untyped,
+                primitive: Primitive::Untyped,
+                style: Style::None,
             })]
         })
     );
@@ -208,7 +219,8 @@
                 min_field_width: MinFieldWidth::None,
                 precision: Precision::None,
                 length: None,
-                specifier: Specifier::Hex,
+                primitive: Primitive::Untyped,
+                style: Style::Hex,
             })]
         })
     );
@@ -226,7 +238,8 @@
                 min_field_width: MinFieldWidth::Fixed(10),
                 precision: Precision::None,
                 length: None,
-                specifier: Specifier::Hex,
+                primitive: Primitive::Untyped,
+                style: Style::Hex,
             })]
         })
     );
@@ -242,7 +255,8 @@
                 min_field_width: MinFieldWidth::None,
                 precision: Precision::Fixed(5),
                 length: None,
-                specifier: Specifier::Untyped,
+                primitive: Primitive::Untyped,
+                style: Style::None,
             })]
         })
     );
@@ -258,7 +272,8 @@
                 min_field_width: MinFieldWidth::None,
                 precision: Precision::Variable,
                 length: None,
-                specifier: Specifier::Untyped,
+                primitive: Primitive::Untyped,
+                style: Style::None,
             })]
         })
     );
@@ -274,7 +289,8 @@
                 min_field_width: MinFieldWidth::None,
                 precision: Precision::Variable,
                 length: None,
-                specifier: Specifier::Untyped,
+                primitive: Primitive::Untyped,
+                style: Style::None,
             })]
         })
     );
@@ -290,7 +306,8 @@
                 min_field_width: MinFieldWidth::None,
                 precision: Precision::Variable,
                 length: None,
-                specifier: Specifier::Untyped,
+                primitive: Primitive::Untyped,
+                style: Style::None,
             })]
         })
     );
@@ -306,7 +323,8 @@
                 min_field_width: MinFieldWidth::Fixed(8),
                 precision: Precision::Variable,
                 length: None,
-                specifier: Specifier::Untyped,
+                primitive: Primitive::Untyped,
+                style: Style::None,
             })]
         })
     );
@@ -325,7 +343,8 @@
                 min_field_width: MinFieldWidth::Fixed(8),
                 precision: Precision::Variable,
                 length: None,
-                specifier: Specifier::Untyped,
+                primitive: Primitive::Untyped,
+                style: Style::None,
             })]
         })
     );
diff --git a/pw_format/rust/pw_format/tests/printf.rs b/pw_format/rust/pw_format/tests/printf.rs
index d87ad15..2ec2f97 100644
--- a/pw_format/rust/pw_format/tests/printf.rs
+++ b/pw_format/rust/pw_format/tests/printf.rs
@@ -17,7 +17,7 @@
 #[test]
 fn test_parse() {
     assert_eq!(
-        FormatString::parse_printf("long double %+ 4.2Lg is %-03hd%%."),
+        FormatString::parse_printf("long double %+ 4.2Lf is %-03hd%%."),
         Ok(FormatString {
             fragments: vec![
                 FormatFragment::Literal("long double ".to_string()),
@@ -29,7 +29,8 @@
                     min_field_width: MinFieldWidth::Fixed(4),
                     precision: Precision::Fixed(2),
                     length: Some(Length::LongDouble),
-                    specifier: Specifier::SmallDouble
+                    primitive: Primitive::Float,
+                    style: Style::None,
                 }),
                 FormatFragment::Literal(" is ".to_string()),
                 FormatFragment::Conversion(ConversionSpec {
@@ -42,7 +43,8 @@
                     min_field_width: MinFieldWidth::Fixed(3),
                     precision: Precision::None,
                     length: Some(Length::Short),
-                    specifier: Specifier::Decimal
+                    primitive: Primitive::Integer,
+                    style: Style::None,
                 }),
                 FormatFragment::Literal("%.".to_string()),
             ]
@@ -130,20 +132,20 @@
     assert!(FormatString::parse_printf("%*%").is_err());
 }
 
-const INTEGERS: &'static [(&'static str, Specifier)] = &[
-    ("d", Specifier::Decimal),
-    ("i", Specifier::Integer),
-    ("o", Specifier::Octal),
-    ("u", Specifier::Unsigned),
-    ("x", Specifier::Hex),
-    ("X", Specifier::UpperHex),
+const INTEGERS: &'static [(&'static str, Primitive, Style)] = &[
+    ("d", Primitive::Integer, Style::None),
+    ("i", Primitive::Integer, Style::None),
+    ("o", Primitive::Unsigned, Style::Octal),
+    ("u", Primitive::Unsigned, Style::None),
+    ("x", Primitive::Unsigned, Style::Hex),
+    ("X", Primitive::Unsigned, Style::UpperHex),
     // While not strictly an integer pointers take the same args as integers.
-    ("p", Specifier::Pointer),
+    ("p", Primitive::Pointer, Style::Pointer),
 ];
 
 #[test]
 fn test_integer() {
-    for (format_char, specifier) in INTEGERS {
+    for (format_char, primitive, style) in INTEGERS {
         assert_eq!(
             FormatString::parse_printf(&format!("%{format_char}")),
             Ok(FormatString {
@@ -155,7 +157,8 @@
                     min_field_width: MinFieldWidth::None,
                     precision: Precision::None,
                     length: None,
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -164,7 +167,7 @@
 
 #[test]
 fn test_integer_with_minus() {
-    for (format_char, specifier) in INTEGERS {
+    for (format_char, primitive, style) in INTEGERS {
         assert_eq!(
             FormatString::parse_printf(&format!("%-5{format_char}")),
             Ok(FormatString {
@@ -176,7 +179,8 @@
                     min_field_width: MinFieldWidth::Fixed(5),
                     precision: Precision::None,
                     length: None,
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -185,7 +189,7 @@
 
 #[test]
 fn test_integer_with_plus() {
-    for (format_char, specifier) in INTEGERS {
+    for (format_char, primitive, style) in INTEGERS {
         assert_eq!(
             FormatString::parse_printf(&format!("%+{format_char}")),
             Ok(FormatString {
@@ -197,7 +201,8 @@
                     min_field_width: MinFieldWidth::None,
                     precision: Precision::None,
                     length: None,
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -206,7 +211,7 @@
 
 #[test]
 fn test_integer_with_blank_space() {
-    for (format_char, specifier) in INTEGERS {
+    for (format_char, primitive, style) in INTEGERS {
         assert_eq!(
             FormatString::parse_printf(&format!("% {format_char}")),
             Ok(FormatString {
@@ -218,7 +223,8 @@
                     min_field_width: MinFieldWidth::None,
                     precision: Precision::None,
                     length: None,
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -227,7 +233,7 @@
 
 #[test]
 fn test_integer_with_plus_and_blank_space_ignores_blank_space() {
-    for (format_char, specifier) in INTEGERS {
+    for (format_char, primitive, style) in INTEGERS {
         assert_eq!(
             FormatString::parse_printf(&format!("%+ {format_char}")),
             Ok(FormatString {
@@ -239,7 +245,8 @@
                     min_field_width: MinFieldWidth::None,
                     precision: Precision::None,
                     length: None,
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -255,7 +262,8 @@
                     min_field_width: MinFieldWidth::None,
                     precision: Precision::None,
                     length: None,
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -264,7 +272,7 @@
 
 #[test]
 fn test_integer_with_hash() {
-    for (format_char, specifier) in INTEGERS {
+    for (format_char, primitive, style) in INTEGERS {
         assert_eq!(
             FormatString::parse_printf(&format!("%#{format_char}")),
             Ok(FormatString {
@@ -276,7 +284,8 @@
                     min_field_width: MinFieldWidth::None,
                     precision: Precision::None,
                     length: None,
-                    specifier: specifier.clone(),
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -285,7 +294,7 @@
 
 #[test]
 fn test_integer_with_zero() {
-    for (format_char, specifier) in INTEGERS {
+    for (format_char, primitive, style) in INTEGERS {
         assert_eq!(
             FormatString::parse_printf(&format!("%0{format_char}")),
             Ok(FormatString {
@@ -297,7 +306,8 @@
                     min_field_width: MinFieldWidth::None,
                     precision: Precision::None,
                     length: None,
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -306,7 +316,7 @@
 
 #[test]
 fn test_integer_with_length() {
-    for (format_char, specifier) in INTEGERS {
+    for (format_char, primitive, style) in INTEGERS {
         assert_eq!(
             FormatString::parse_printf(&format!("%hh{format_char}")),
             Ok(FormatString {
@@ -318,7 +328,8 @@
                     min_field_width: MinFieldWidth::None,
                     precision: Precision::None,
                     length: Some(Length::Char),
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -334,7 +345,8 @@
                     min_field_width: MinFieldWidth::None,
                     precision: Precision::None,
                     length: Some(Length::Short),
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -350,7 +362,8 @@
                     min_field_width: MinFieldWidth::None,
                     precision: Precision::None,
                     length: Some(Length::Long),
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -366,7 +379,8 @@
                     min_field_width: MinFieldWidth::None,
                     precision: Precision::None,
                     length: Some(Length::LongLong),
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -382,7 +396,8 @@
                     min_field_width: MinFieldWidth::None,
                     precision: Precision::None,
                     length: Some(Length::IntMax),
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -398,7 +413,8 @@
                     min_field_width: MinFieldWidth::None,
                     precision: Precision::None,
                     length: Some(Length::Size),
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -414,25 +430,23 @@
                     min_field_width: MinFieldWidth::None,
                     precision: Precision::None,
                     length: Some(Length::PointerDiff),
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
     }
 }
 
-const FLOATS: &'static [(&'static str, Specifier)] = &[
-    ("f", Specifier::Double),
-    ("F", Specifier::UpperDouble),
-    ("e", Specifier::Exponential),
-    ("E", Specifier::UpperExponential),
-    ("g", Specifier::SmallDouble),
-    ("G", Specifier::UpperSmallDouble),
+const FLOATS: &'static [(&'static str, Primitive, Style)] = &[
+    ("f", Primitive::Float, Style::None),
+    ("e", Primitive::Float, Style::Exponential),
+    ("E", Primitive::Float, Style::UpperExponential),
 ];
 
 #[test]
 fn test_float() {
-    for (format_char, specifier) in FLOATS {
+    for (format_char, primitive, style) in FLOATS {
         assert_eq!(
             FormatString::parse_printf(&format!("%{format_char}")),
             Ok(FormatString {
@@ -444,7 +458,8 @@
                     min_field_width: MinFieldWidth::None,
                     precision: Precision::None,
                     length: None,
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -453,7 +468,7 @@
 
 #[test]
 fn test_float_with_minus() {
-    for (format_char, specifier) in FLOATS {
+    for (format_char, primitive, style) in FLOATS {
         assert_eq!(
             FormatString::parse_printf(&format!("%-10{format_char}")),
             Ok(FormatString {
@@ -465,7 +480,8 @@
                     min_field_width: MinFieldWidth::Fixed(10),
                     precision: Precision::None,
                     length: None,
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -474,7 +490,7 @@
 
 #[test]
 fn test_float_with_plus() {
-    for (format_char, specifier) in FLOATS {
+    for (format_char, primitive, style) in FLOATS {
         assert_eq!(
             FormatString::parse_printf(&format!("%+{format_char}")),
             Ok(FormatString {
@@ -486,7 +502,8 @@
                     min_field_width: MinFieldWidth::None,
                     precision: Precision::None,
                     length: None,
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -495,7 +512,7 @@
 
 #[test]
 fn test_float_with_blank_space() {
-    for (format_char, specifier) in FLOATS {
+    for (format_char, primitive, style) in FLOATS {
         assert_eq!(
             FormatString::parse_printf(&format!("% {format_char}")),
             Ok(FormatString {
@@ -507,7 +524,8 @@
                     min_field_width: MinFieldWidth::None,
                     precision: Precision::None,
                     length: None,
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -516,7 +534,7 @@
 
 #[test]
 fn test_float_with_plus_and_blank_space_ignores_blank_space() {
-    for (format_char, specifier) in FLOATS {
+    for (format_char, primitive, style) in FLOATS {
         assert_eq!(
             FormatString::parse_printf(&format!("%+ {format_char}")),
             Ok(FormatString {
@@ -528,7 +546,8 @@
                     min_field_width: MinFieldWidth::None,
                     precision: Precision::None,
                     length: None,
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -544,7 +563,8 @@
                     min_field_width: MinFieldWidth::None,
                     precision: Precision::None,
                     length: None,
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -553,7 +573,7 @@
 
 #[test]
 fn test_float_with_hash() {
-    for (format_char, specifier) in FLOATS {
+    for (format_char, primitive, style) in FLOATS {
         assert_eq!(
             FormatString::parse_printf(&format!("%.0{format_char}")),
             Ok(FormatString {
@@ -565,7 +585,8 @@
                     min_field_width: MinFieldWidth::None,
                     precision: Precision::Fixed(0),
                     length: None,
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -581,7 +602,8 @@
                     min_field_width: MinFieldWidth::None,
                     precision: Precision::Fixed(0),
                     length: None,
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -590,7 +612,7 @@
 
 #[test]
 fn test_float_with_zero() {
-    for (format_char, specifier) in FLOATS {
+    for (format_char, primitive, style) in FLOATS {
         assert_eq!(
             FormatString::parse_printf(&format!("%010{format_char}")),
             Ok(FormatString {
@@ -602,7 +624,8 @@
                     min_field_width: MinFieldWidth::Fixed(10),
                     precision: Precision::None,
                     length: None,
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -611,7 +634,7 @@
 
 #[test]
 fn test_float_with_length() {
-    for (format_char, specifier) in FLOATS {
+    for (format_char, primitive, style) in FLOATS {
         assert_eq!(
             FormatString::parse_printf(&format!("%hh{format_char}")),
             Ok(FormatString {
@@ -623,7 +646,8 @@
                     min_field_width: MinFieldWidth::None,
                     precision: Precision::None,
                     length: Some(Length::Char),
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -639,7 +663,8 @@
                     min_field_width: MinFieldWidth::None,
                     precision: Precision::None,
                     length: Some(Length::Short),
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -655,7 +680,8 @@
                     min_field_width: MinFieldWidth::None,
                     precision: Precision::None,
                     length: Some(Length::Long),
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -671,7 +697,8 @@
                     min_field_width: MinFieldWidth::None,
                     precision: Precision::None,
                     length: Some(Length::LongLong),
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -687,7 +714,8 @@
                     min_field_width: MinFieldWidth::None,
                     precision: Precision::None,
                     length: Some(Length::IntMax),
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -703,7 +731,8 @@
                     min_field_width: MinFieldWidth::None,
                     precision: Precision::None,
                     length: Some(Length::Size),
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -719,7 +748,8 @@
                     min_field_width: MinFieldWidth::None,
                     precision: Precision::None,
                     length: Some(Length::PointerDiff),
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -735,7 +765,8 @@
                     min_field_width: MinFieldWidth::None,
                     precision: Precision::None,
                     length: Some(Length::LongDouble),
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -744,7 +775,7 @@
 
 #[test]
 fn test_float_with_width() {
-    for (format_char, specifier) in FLOATS {
+    for (format_char, primitive, style) in FLOATS {
         assert_eq!(
             FormatString::parse_printf(&format!("%9{format_char}")),
             Ok(FormatString {
@@ -756,7 +787,8 @@
                     min_field_width: MinFieldWidth::Fixed(9),
                     precision: Precision::None,
                     length: None,
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -765,7 +797,7 @@
 
 #[test]
 fn test_float_with_multidigit_width() {
-    for (format_char, specifier) in FLOATS {
+    for (format_char, primitive, style) in FLOATS {
         assert_eq!(
             FormatString::parse_printf(&format!("%10{format_char}")),
             Ok(FormatString {
@@ -777,7 +809,8 @@
                     min_field_width: MinFieldWidth::Fixed(10),
                     precision: Precision::None,
                     length: None,
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -786,7 +819,7 @@
 
 #[test]
 fn test_float_with_star_width() {
-    for (format_char, specifier) in FLOATS {
+    for (format_char, primitive, style) in FLOATS {
         assert_eq!(
             FormatString::parse_printf(&format!("%*{format_char}")),
             Ok(FormatString {
@@ -798,7 +831,8 @@
                     min_field_width: MinFieldWidth::Variable,
                     precision: Precision::None,
                     length: None,
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -807,7 +841,7 @@
 
 #[test]
 fn test_float_with_precision() {
-    for (format_char, specifier) in FLOATS {
+    for (format_char, primitive, style) in FLOATS {
         assert_eq!(
             FormatString::parse_printf(&format!("%.4{format_char}")),
             Ok(FormatString {
@@ -819,7 +853,8 @@
                     min_field_width: MinFieldWidth::None,
                     precision: Precision::Fixed(4),
                     length: None,
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -828,7 +863,7 @@
 
 #[test]
 fn test_float_with_multidigit_precision() {
-    for (format_char, specifier) in FLOATS {
+    for (format_char, primitive, style) in FLOATS {
         assert_eq!(
             FormatString::parse_printf(&format!("%.10{format_char}")),
             Ok(FormatString {
@@ -840,7 +875,8 @@
                     min_field_width: MinFieldWidth::None,
                     precision: Precision::Fixed(10),
                     length: None,
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -849,7 +885,7 @@
 
 #[test]
 fn test_float_with_star_precision() {
-    for (format_char, specifier) in FLOATS {
+    for (format_char, primitive, style) in FLOATS {
         assert_eq!(
             FormatString::parse_printf(&format!("%.*{format_char}")),
             Ok(FormatString {
@@ -861,7 +897,8 @@
                     min_field_width: MinFieldWidth::None,
                     precision: Precision::Variable,
                     length: None,
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -870,7 +907,7 @@
 
 #[test]
 fn test_float_with_star_width_and_star_precision() {
-    for (format_char, specifier) in FLOATS {
+    for (format_char, primitive, style) in FLOATS {
         assert_eq!(
             FormatString::parse_printf(&format!("%*.*{format_char}")),
             Ok(FormatString {
@@ -882,7 +919,8 @@
                     min_field_width: MinFieldWidth::Variable,
                     precision: Precision::Variable,
                     length: None,
-                    specifier: specifier.clone()
+                    primitive: *primitive,
+                    style: *style,
                 })]
             })
         );
@@ -902,7 +940,8 @@
                 min_field_width: MinFieldWidth::None,
                 precision: Precision::None,
                 length: None,
-                specifier: Specifier::Char
+                primitive: Primitive::Character,
+                style: Style::None,
             })]
         })
     );
@@ -921,7 +960,8 @@
                 min_field_width: MinFieldWidth::Fixed(5),
                 precision: Precision::None,
                 length: None,
-                specifier: Specifier::Char
+                primitive: Primitive::Character,
+                style: Style::None,
             })]
         })
     );
@@ -966,7 +1006,8 @@
                 min_field_width: MinFieldWidth::None,
                 precision: Precision::None,
                 length: Some(Length::Char),
-                specifier: Specifier::Char
+                primitive: Primitive::Character,
+                style: Style::None,
             })]
         })
     );
@@ -982,7 +1023,8 @@
                 min_field_width: MinFieldWidth::None,
                 precision: Precision::None,
                 length: Some(Length::Short),
-                specifier: Specifier::Char
+                primitive: Primitive::Character,
+                style: Style::None,
             })]
         })
     );
@@ -998,7 +1040,8 @@
                 min_field_width: MinFieldWidth::None,
                 precision: Precision::None,
                 length: Some(Length::Long),
-                specifier: Specifier::Char
+                primitive: Primitive::Character,
+                style: Style::None,
             })]
         })
     );
@@ -1014,7 +1057,8 @@
                 min_field_width: MinFieldWidth::None,
                 precision: Precision::None,
                 length: Some(Length::LongLong),
-                specifier: Specifier::Char
+                primitive: Primitive::Character,
+                style: Style::None,
             })]
         })
     );
@@ -1030,7 +1074,8 @@
                 min_field_width: MinFieldWidth::None,
                 precision: Precision::None,
                 length: Some(Length::IntMax),
-                specifier: Specifier::Char
+                primitive: Primitive::Character,
+                style: Style::None,
             })]
         })
     );
@@ -1046,7 +1091,8 @@
                 min_field_width: MinFieldWidth::None,
                 precision: Precision::None,
                 length: Some(Length::Size),
-                specifier: Specifier::Char
+                primitive: Primitive::Character,
+                style: Style::None,
             })]
         })
     );
@@ -1062,7 +1108,8 @@
                 min_field_width: MinFieldWidth::None,
                 precision: Precision::None,
                 length: Some(Length::PointerDiff),
-                specifier: Specifier::Char
+                primitive: Primitive::Character,
+                style: Style::None,
             })]
         })
     );
@@ -1078,7 +1125,8 @@
                 min_field_width: MinFieldWidth::None,
                 precision: Precision::None,
                 length: Some(Length::LongDouble),
-                specifier: Specifier::Char
+                primitive: Primitive::Character,
+                style: Style::None,
             })]
         })
     );
@@ -1097,7 +1145,8 @@
                 min_field_width: MinFieldWidth::Fixed(5),
                 precision: Precision::None,
                 length: None,
-                specifier: Specifier::Char
+                primitive: Primitive::Character,
+                style: Style::None,
             })]
         })
     );
@@ -1116,7 +1165,8 @@
                 min_field_width: MinFieldWidth::Fixed(10),
                 precision: Precision::None,
                 length: None,
-                specifier: Specifier::Char
+                primitive: Primitive::Character,
+                style: Style::None,
             })]
         })
     );
@@ -1135,7 +1185,8 @@
                 min_field_width: MinFieldWidth::Variable,
                 precision: Precision::None,
                 length: None,
-                specifier: Specifier::Char
+                primitive: Primitive::Character,
+                style: Style::None,
             })]
         })
     );
@@ -1172,7 +1223,8 @@
                 min_field_width: MinFieldWidth::None,
                 precision: Precision::None,
                 length: None,
-                specifier: Specifier::String
+                primitive: Primitive::String,
+                style: Style::None,
             })]
         })
     );
@@ -1191,7 +1243,8 @@
                 min_field_width: MinFieldWidth::Fixed(6),
                 precision: Precision::None,
                 length: None,
-                specifier: Specifier::String
+                primitive: Primitive::String,
+                style: Style::None,
             })]
         })
     );
@@ -1236,7 +1289,8 @@
                 min_field_width: MinFieldWidth::None,
                 precision: Precision::None,
                 length: Some(Length::Char),
-                specifier: Specifier::String
+                primitive: Primitive::String,
+                style: Style::None,
             })]
         })
     );
@@ -1252,7 +1306,8 @@
                 min_field_width: MinFieldWidth::None,
                 precision: Precision::None,
                 length: Some(Length::Short),
-                specifier: Specifier::String
+                primitive: Primitive::String,
+                style: Style::None,
             })]
         })
     );
@@ -1268,7 +1323,8 @@
                 min_field_width: MinFieldWidth::None,
                 precision: Precision::None,
                 length: Some(Length::Long),
-                specifier: Specifier::String
+                primitive: Primitive::String,
+                style: Style::None,
             })]
         })
     );
@@ -1284,7 +1340,8 @@
                 min_field_width: MinFieldWidth::None,
                 precision: Precision::None,
                 length: Some(Length::LongLong),
-                specifier: Specifier::String
+                primitive: Primitive::String,
+                style: Style::None,
             })]
         })
     );
@@ -1300,7 +1357,8 @@
                 min_field_width: MinFieldWidth::None,
                 precision: Precision::None,
                 length: Some(Length::IntMax),
-                specifier: Specifier::String
+                primitive: Primitive::String,
+                style: Style::None,
             })]
         })
     );
@@ -1316,7 +1374,8 @@
                 min_field_width: MinFieldWidth::None,
                 precision: Precision::None,
                 length: Some(Length::Size),
-                specifier: Specifier::String
+                primitive: Primitive::String,
+                style: Style::None,
             })]
         })
     );
@@ -1332,7 +1391,8 @@
                 min_field_width: MinFieldWidth::None,
                 precision: Precision::None,
                 length: Some(Length::PointerDiff),
-                specifier: Specifier::String
+                primitive: Primitive::String,
+                style: Style::None,
             })]
         })
     );
@@ -1348,7 +1408,8 @@
                 min_field_width: MinFieldWidth::None,
                 precision: Precision::None,
                 length: Some(Length::LongDouble),
-                specifier: Specifier::String
+                primitive: Primitive::String,
+                style: Style::None,
             })]
         })
     );
@@ -1367,7 +1428,8 @@
                 min_field_width: MinFieldWidth::Fixed(6),
                 precision: Precision::None,
                 length: None,
-                specifier: Specifier::String
+                primitive: Primitive::String,
+                style: Style::None,
             })]
         })
     );
@@ -1386,7 +1448,8 @@
                 min_field_width: MinFieldWidth::Fixed(10),
                 precision: Precision::None,
                 length: None,
-                specifier: Specifier::String
+                primitive: Primitive::String,
+                style: Style::None,
             })]
         })
     );
@@ -1405,7 +1468,8 @@
                 min_field_width: MinFieldWidth::Variable,
                 precision: Precision::None,
                 length: None,
-                specifier: Specifier::String
+                primitive: Primitive::String,
+                style: Style::None,
             })]
         })
     );
@@ -1424,7 +1488,8 @@
                 min_field_width: MinFieldWidth::None,
                 precision: Precision::Fixed(3),
                 length: None,
-                specifier: Specifier::String
+                primitive: Primitive::String,
+                style: Style::None,
             })]
         })
     );
@@ -1443,7 +1508,8 @@
                 min_field_width: MinFieldWidth::None,
                 precision: Precision::Fixed(10),
                 length: None,
-                specifier: Specifier::String
+                primitive: Primitive::String,
+                style: Style::None,
             })]
         })
     );
@@ -1462,7 +1528,8 @@
                 min_field_width: MinFieldWidth::Fixed(10),
                 precision: Precision::Fixed(3),
                 length: None,
-                specifier: Specifier::String
+                primitive: Primitive::String,
+                style: Style::None,
             })]
         })
     );
@@ -1481,7 +1548,8 @@
                 min_field_width: MinFieldWidth::Variable,
                 precision: Precision::Variable,
                 length: None,
-                specifier: Specifier::String
+                primitive: Primitive::String,
+                style: Style::None,
             })]
         })
     );
diff --git a/pw_format/rust/pw_format_example_macro.rs b/pw_format/rust/pw_format_example_macro.rs
index 2ef567c..775ab9f 100644
--- a/pw_format/rust/pw_format_example_macro.rs
+++ b/pw_format/rust/pw_format_example_macro.rs
@@ -13,9 +13,11 @@
 // the License.
 
 use proc_macro::TokenStream;
-use pw_format::macros::{
-    generate, Arg, FormatAndArgsFlavor, FormatMacroGenerator, IntegerDisplayType,
-    PrintfFormatStringParser, Result,
+use pw_format::{
+    macros::{
+        generate, Arg, FormatAndArgsFlavor, FormatMacroGenerator, PrintfFormatStringParser, Result,
+    },
+    Style,
 };
 use quote::quote;
 use syn::{
@@ -102,7 +104,8 @@
     // This example ignores display type and width.
     fn integer_conversion(
         &mut self,
-        _display: IntegerDisplayType,
+        _style: Style,
+        _signed: bool,
         _type_width: u8, // in bits
         expression: Arg,
     ) -> Result<()> {
diff --git a/pw_format/rust/pw_format_test_macros.rs b/pw_format/rust/pw_format_test_macros.rs
index 445edff..46e5aa7 100644
--- a/pw_format/rust/pw_format_test_macros.rs
+++ b/pw_format/rust/pw_format_test_macros.rs
@@ -14,11 +14,13 @@
 
 use proc_macro::TokenStream;
 use proc_macro2::Ident;
-use pw_format::macros::{
-    generate, generate_core_fmt, generate_printf, Arg, CoreFmtFormatMacroGenerator,
-    CoreFmtFormatStringParser, FormatAndArgsFlavor, FormatMacroGenerator, FormatStringParser,
-    IntegerDisplayType, PrintfFormatMacroGenerator, PrintfFormatStringFragment,
-    PrintfFormatStringParser, Result,
+use pw_format::{
+    macros::{
+        generate, generate_core_fmt, generate_printf, Arg, CoreFmtFormatMacroGenerator,
+        CoreFmtFormatStringParser, FormatAndArgsFlavor, FormatMacroGenerator, FormatStringParser,
+        PrintfFormatMacroGenerator, PrintfFormatStringFragment, PrintfFormatStringParser, Result,
+    },
+    Style,
 };
 use quote::{quote, ToTokens};
 use syn::parse_macro_input;
@@ -63,14 +65,16 @@
     // This example ignores display type and width.
     fn integer_conversion(
         &mut self,
-        display_type: IntegerDisplayType,
+        style: Style,
+        signed: bool,
         type_width: u8, // in bits
         expression: Arg,
     ) -> Result<()> {
         let expression = format!("{}", expression.to_token_stream());
         self.code_fragments.push(quote! {
             ops.push(TestGeneratorOps::IntegerConversion{
-                display_type: #display_type,
+                style: #style,
+                signed: #signed,
                 type_width: #type_width,
                 arg: #expression.to_string(),
             });
diff --git a/pw_format/rust/pw_format_test_macros_core_fmt_test.rs b/pw_format/rust/pw_format_test_macros_core_fmt_test.rs
index 81bac71..ecb52f0 100644
--- a/pw_format/rust/pw_format_test_macros_core_fmt_test.rs
+++ b/pw_format/rust/pw_format_test_macros_core_fmt_test.rs
@@ -12,7 +12,7 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-use pw_format::macros::IntegerDisplayType;
+use pw_format::Style;
 
 // Used to record calls into the test generator from `generator_test_macro!`.
 #[derive(Debug, PartialEq)]
@@ -20,7 +20,8 @@
     Finalize,
     StringFragment(String),
     IntegerConversion {
-        display_type: IntegerDisplayType,
+        style: Style,
+        signed: bool,
         type_width: u8,
         arg: String,
     },
diff --git a/pw_format/rust/pw_format_test_macros_printf_test.rs b/pw_format/rust/pw_format_test_macros_printf_test.rs
index 1b2f4c6..3a9bfa5 100644
--- a/pw_format/rust/pw_format_test_macros_printf_test.rs
+++ b/pw_format/rust/pw_format_test_macros_printf_test.rs
@@ -12,7 +12,7 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-use pw_format::macros::IntegerDisplayType;
+use pw_format::Style;
 
 // Used to record calls into the test generator from `generator_test_macro!`.
 #[derive(Debug, PartialEq)]
@@ -20,7 +20,8 @@
     Finalize,
     StringFragment(String),
     IntegerConversion {
-        display_type: IntegerDisplayType,
+        style: Style,
+        signed: bool,
         type_width: u8,
         arg: String,
     },
@@ -65,7 +66,8 @@
             vec![
                 TestGeneratorOps::StringFragment("test ".to_string()),
                 TestGeneratorOps::IntegerConversion {
-                    display_type: IntegerDisplayType::Signed,
+                    style: Style::None,
+                    signed: true,
                     type_width: 32,
                     arg: "5".to_string(),
                 },