use crate::{PrintfError, Result};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FormatElement<'a> {
Verbatim(&'a str),
Format(ConversionSpecifier),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ConversionSpecifier {
pub alt_form: bool,
pub zero_pad: bool,
pub left_adj: bool,
pub space_sign: bool,
pub force_sign: bool,
pub width: NumericParam,
pub precision: NumericParam,
pub conversion_type: ConversionType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NumericParam {
Literal(i32),
FromArgument,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConversionType {
DecInt,
OctInt,
HexIntLower,
HexIntUpper,
SciFloatLower,
SciFloatUpper,
DecFloatLower,
DecFloatUpper,
CompactFloatLower,
CompactFloatUpper,
Char,
String,
PercentSign,
}
pub fn parse_format_string(fmt: &str) -> Result<Vec<FormatElement<'_>>> {
let mut res = Vec::new();
let mut rem = fmt;
while !rem.is_empty() {
if let Some((verbatim_prefix, rest)) = rem.split_once('%') {
if !verbatim_prefix.is_empty() {
res.push(FormatElement::Verbatim(verbatim_prefix));
}
let (spec, rest) = take_conversion_specifier(rest)?;
res.push(FormatElement::Format(spec));
rem = rest;
} else {
res.push(FormatElement::Verbatim(rem));
break;
}
}
Ok(res)
}
fn take_conversion_specifier(s: &str) -> Result<(ConversionSpecifier, &str)> {
let mut spec = ConversionSpecifier {
alt_form: false,
zero_pad: false,
left_adj: false,
space_sign: false,
force_sign: false,
width: NumericParam::Literal(0),
precision: NumericParam::FromArgument, conversion_type: ConversionType::DecInt,
};
let mut s = s;
loop {
match s.chars().next() {
Some('#') => {
spec.alt_form = true;
}
Some('0') => {
spec.zero_pad = true;
}
Some('-') => {
spec.left_adj = true;
}
Some(' ') => {
spec.space_sign = true;
}
Some('+') => {
spec.force_sign = true;
}
_ => {
break;
}
}
s = &s[1..];
}
let (w, mut s) = take_numeric_param(s);
spec.width = w;
if matches!(s.chars().next(), Some('.')) {
s = &s[1..];
let (p, s2) = take_numeric_param(s);
spec.precision = p;
s = s2;
}
for len_spec in ["hh", "h", "ll", "l", "q", "L", "j", "z", "Z", "t"] {
if s.starts_with(len_spec) {
s = s.strip_prefix(len_spec).ok_or(PrintfError::ParseError)?;
break; }
}
spec.conversion_type = match s.chars().next() {
Some('i') | Some('d') | Some('u') => ConversionType::DecInt,
Some('o') => ConversionType::OctInt,
Some('x') => ConversionType::HexIntLower,
Some('X') => ConversionType::HexIntUpper,
Some('e') => ConversionType::SciFloatLower,
Some('E') => ConversionType::SciFloatUpper,
Some('f') => ConversionType::DecFloatLower,
Some('F') => ConversionType::DecFloatUpper,
Some('g') => ConversionType::CompactFloatLower,
Some('G') => ConversionType::CompactFloatUpper,
Some('c') | Some('C') => ConversionType::Char,
Some('s') | Some('S') => ConversionType::String,
Some('p') => {
spec.alt_form = true;
ConversionType::HexIntLower
}
Some('%') => ConversionType::PercentSign,
_ => {
return Err(PrintfError::ParseError);
}
};
if spec.precision == NumericParam::FromArgument {
let p = if spec.conversion_type == ConversionType::String {
i32::MAX
} else {
6
};
spec.precision = NumericParam::Literal(p);
}
Ok((spec, &s[1..]))
}
fn take_numeric_param(s: &str) -> (NumericParam, &str) {
match s.chars().next() {
Some('*') => (NumericParam::FromArgument, &s[1..]),
Some(digit) if ('0'..='9').contains(&digit) => {
let mut s = s;
let mut w = 0;
loop {
match s.chars().next() {
Some(digit) if ('0'..='9').contains(&digit) => {
w = 10 * w + (digit as i32 - '0' as i32);
}
_ => {
break;
}
}
s = &s[1..];
}
(NumericParam::Literal(w), s)
}
_ => (NumericParam::Literal(0), s),
}
}