use crate::Float;
use crate::InnerFloat::{Finite, Infinity, NaN, Zero};
use crate::float::conversion::string::get_str::{ceil_mul, get_str, get_str_ndigits};
use alloc::format;
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use core::cmp::Ordering::{Equal, Greater, Less};
use malachite_base::fail_on_untested_path;
use malachite_base::num::basic::integers::PrimitiveInt;
use malachite_base::num::basic::traits::{One, OneHalf};
use malachite_base::num::comparison::traits::PartialOrdAbs;
use malachite_base::num::conversion::string::to_string::digit_to_display_byte_lower;
use malachite_base::num::conversion::traits::ExactFrom;
use malachite_base::num::logic::traits::{BitAccess, LowMask, SignificantBits};
use malachite_base::rounding_modes::RoundingMode::{
self, Ceiling, Down, Exact, Floor, Nearest, Up,
};
use malachite_nz::natural::Natural;
use malachite_nz::platform::Limb;
#[derive(Clone, Copy, Eq, PartialEq)]
pub(crate) enum ArgType {
None,
Char,
Short,
Long,
LongLong,
IntMax,
Size,
PtrDiff,
LongDouble,
Mpf,
Mpq,
MpLimb,
MpLimbArray,
Mpz,
MpfrPrec,
Mpfr,
}
pub_crate_test_struct! {
#[derive(Clone, Copy)]
PrintfSpec {
pub(crate) alt: bool, pub(crate) space: bool, pub(crate) left: bool, pub(crate) showsign: bool, pub(crate) group: bool, pub(crate) width: i64,
pub(crate) prec: i64,
pub(crate) arg_type: ArgType,
pub(crate) rnd_mode: RoundingMode,
pub(crate) spec: u8,
pub(crate) pad: u8,
}}
const fn specinfo_init() -> PrintfSpec {
PrintfSpec {
alt: false,
space: false,
left: false,
showsign: false,
group: false,
width: 0,
prec: 0,
arg_type: ArgType::None,
rnd_mode: Nearest,
spec: b'\0',
pad: b' ',
}
}
const fn floating_point_arg_type(at: ArgType) -> bool {
matches!(
at,
ArgType::Mpfr | ArgType::Mpf | ArgType::Long | ArgType::LongDouble
)
}
const fn integer_like_arg_type(at: ArgType) -> bool {
matches!(
at,
ArgType::Short
| ArgType::Long
| ArgType::LongLong
| ArgType::IntMax
| ArgType::MpfrPrec
| ArgType::Mpz
| ArgType::Mpq
| ArgType::MpLimb
| ArgType::MpLimbArray
| ArgType::Char
| ArgType::Size
| ArgType::PtrDiff
)
}
fn specinfo_is_valid(spec: PrintfSpec) -> bool {
match spec.spec {
b'a' | b'A' | b'e' | b'E' | b'f' | b'g' | b'G' => {
spec.arg_type == ArgType::None || floating_point_arg_type(spec.arg_type)
}
b'F' | b'b' => spec.arg_type == ArgType::Mpfr,
b'd' | b'i' | b'o' | b'u' | b'x' | b'X' => {
spec.arg_type == ArgType::None || integer_like_arg_type(spec.arg_type)
}
b'c' | b's' => matches!(spec.arg_type, ArgType::None | ArgType::Long),
b'p' => spec.arg_type == ArgType::None,
_ => false,
}
}
fn parse_flags<'a>(mut format: &'a [u8], specinfo: &mut PrintfSpec) -> &'a [u8] {
while let Some(&c) = format.first() {
match c {
b'0' => {
specinfo.pad = b'0';
}
b'#' => {
specinfo.alt = true;
}
b'+' => {
specinfo.showsign = true;
}
b' ' => {
specinfo.space = true;
}
b'-' => {
specinfo.left = true;
}
b'\'' => {
specinfo.group = true;
}
_ => {
return format;
}
}
format = &format[1..];
}
format
}
const fn parse_arg_type<'a>(format: &'a [u8], specinfo: &mut PrintfSpec) -> &'a [u8] {
let Some((&format_head, mut format_tail)) = format.split_first() else {
return format;
};
specinfo.arg_type = match format_head {
b'h' => {
if let Some((b'h', tail)) = format_tail.split_first() {
format_tail = tail;
ArgType::Char
} else {
ArgType::Short
}
}
b'l' => {
if let Some((b'l', tail)) = format_tail.split_first() {
format_tail = tail;
ArgType::LongLong
} else {
ArgType::Long
}
}
b'j' => ArgType::IntMax,
b'z' => ArgType::Size,
b't' => ArgType::PtrDiff,
b'L' => ArgType::LongDouble,
b'F' => ArgType::Mpf,
b'Q' => ArgType::Mpq,
b'M' => ArgType::MpLimb,
b'N' => ArgType::MpLimbArray,
b'Z' => ArgType::Mpz,
b'P' => ArgType::MpfrPrec,
b'R' => ArgType::Mpfr,
_ => return format,
};
format_tail
}
fn buffer_pad(b: &mut Vec<u8>, c: u8, n: i64) {
let new_len = b.len() + usize::exact_from(n);
b.resize(new_len, c);
}
fn buffer_sandwich(b: &mut Vec<u8>, mut str: &[u8], tz: usize, c: u8) {
const STEP: usize = 3;
assert!(tz == 0 || tz == 1);
assert!(c != b'\0');
let size = str.len() + tz; assert!(size > 0);
let q = (size - 1) / STEP; let r = ((size - 1) % STEP) + 1; if r <= str.len() {
b.extend_from_slice(&str[..r]);
str = &str[r..];
} else {
b.extend_from_slice(str);
b.push(b'0'); }
for _ in 0..q {
b.push(c);
if str.len() >= STEP {
b.extend_from_slice(&str[..STEP]);
str = &str[STEP..];
} else {
b.extend_from_slice(str);
b.push(b'0'); }
}
}
enum PadType {
Left, LeadingZeros, Right, }
struct NumberParts {
pad_type: PadType,
pad_size: i64,
sign: u8, prefix: &'static [u8], thousands_sep: u8, ip: Vec<u8>, ip_trailing_digits: i32, point: u8, fp_leading_zeros: i64, fp: Vec<u8>, fp_trailing_zeros: i64, exp: Vec<u8>, }
pub(crate) fn strip_trailing_zeros(mut s: &[u8]) -> &[u8] {
while let [rest @ .., b'0'] = s {
s = rest;
}
s
}
fn skip_sign<'a>(str: &'a [u8], p: &Float) -> &'a [u8] {
if p.is_sign_negative() { &str[1..] } else { str }
}
fn nsd_for_prec(prec: i64) -> Option<usize> {
if prec < 0 {
Some(0)
} else {
let nsd = usize::try_from(prec).ok().and_then(|p| p.checked_add(1));
if nsd.is_none() {
fail_on_untested_path("nsd_for_prec, nsd overflows usize");
}
nsd
}
}
fn exponent_part(marker: u8, exp: i64, min_digits: usize) -> Vec<u8> {
let mut out = vec![marker, if exp >= 0 { b'+' } else { b'-' }];
out.extend_from_slice(format!("{:0min_digits$}", exp.unsigned_abs()).as_bytes());
out
}
struct DecimalInfo {
exp: i64,
str: Vec<u8>,
}
fn mpfr_get_str_wrapper(base: i64, n: usize, op: &Float, spec: &PrintfSpec) -> (Vec<u8>, i64) {
let (s, exp, _) = get_str(op, base, n, spec.rnd_mode).unwrap();
(s, exp)
}
fn floor_log10(x: &Float) -> i64 {
let prec = x.get_prec().unwrap().max(i64::BITS.into());
let exp = ceil_mul(i64::from(x.get_exponent().unwrap()), 10, 1) - 1;
let y = Float::power_of_10_of_float_prec_round(Float::from(exp), prec, Up).0;
if x.lt_abs(&y) { exp - 1 } else { exp }
}
const fn number_parts_init() -> NumberParts {
NumberParts {
pad_type: PadType::Right,
pad_size: 0,
sign: b'\0',
prefix: b"",
thousands_sep: b'\0',
ip: Vec::new(),
ip_trailing_digits: 0,
point: b'\0',
fp_leading_zeros: 0,
fp: Vec::new(),
fp_trailing_zeros: 0,
exp: Vec::new(),
}
}
fn regular_eg(
np: &mut NumberParts,
p: &Float,
spec: &PrintfSpec,
dec_info: Option<&DecimalInfo>,
keep_trailing_zeros: bool,
) -> Option<()> {
let uppercase = matches!(spec.spec, b'E' | b'G');
let storage;
let (str, exp): (&[u8], i64) = match dec_info {
None => {
debug_assert!(keep_trailing_zeros);
storage = mpfr_get_str_wrapper(10, nsd_for_prec(spec.prec)?, p, spec);
(&storage.0, storage.1)
}
Some(d) => (&d.str, d.exp),
};
let digits = skip_sign(str, p);
np.ip = vec![digits[0]];
if spec.prec != 0 {
let mut frac = &digits[1..];
if !keep_trailing_zeros {
frac = strip_trailing_zeros(frac);
}
let str_len = frac.len();
if str_len != 0 {
np.fp = frac.to_vec();
debug_assert!(spec.prec < 0 || i64::exact_from(str_len) <= spec.prec);
if keep_trailing_zeros && spec.prec > 0 && i64::exact_from(str_len) < spec.prec {
np.fp_trailing_zeros = spec.prec - i64::exact_from(str_len);
}
}
}
if !np.fp.is_empty() || spec.alt {
np.point = b'.';
}
let exp = exp - 1;
np.exp = exponent_part(if uppercase { b'E' } else { b'e' }, exp, 2);
Some(())
}
fn regular_fg(
np: &mut NumberParts,
p: &Float,
spec: &PrintfSpec,
dec_info: Option<&DecimalInfo>,
keep_trailing_zeros: bool,
) -> Option<()> {
debug_assert!(spec.prec >= 0);
if p.get_exponent().unwrap() <= 0 {
np.ip = vec![b'0'];
if spec.prec == 0 {
assert!(
spec.rnd_mode != Exact,
"regular_fg: Exact rounding was requested, but {p} is not exactly representable \
with 0 fractional digits",
);
let round_up = match spec.rnd_mode {
Floor => p.is_sign_negative(),
Ceiling => p.is_sign_positive(),
Up => true,
Nearest => p.partial_cmp_abs(&Float::ONE_HALF).unwrap() == Greater,
_ => false,
};
if round_up {
np.ip[0] = b'1';
}
} else {
let exp = floor_log10(p);
debug_assert!(exp < 0);
if exp < -spec.prec {
let round_away = match spec.rnd_mode {
Up => true,
Down => false,
Floor => p.is_sign_negative(),
Ceiling => p.is_sign_positive(),
Exact => panic!(
"regular_fg: Exact rounding was requested, but {p} is not exactly \
representable with {} fractional digits",
spec.prec
),
Nearest => {
let mut e = p.get_prec().unwrap().max(56);
loop {
e += 8;
let y = Float::power_of_10_of_float_prec_round(
Float::from(-spec.prec),
e,
Down,
)
.0 >> 1u64;
let cmp = y.partial_cmp_abs(p).unwrap();
if cmp != Equal {
break cmp == Less;
}
}
}
};
np.fp_leading_zeros = if round_away {
np.fp = vec![b'1'];
spec.prec - 1
} else {
debug_assert!(spec.spec == b'f' || spec.spec == b'F');
spec.prec
};
} else {
let storage;
let (str, exp): (&[u8], i64) = match dec_info {
None => {
debug_assert!(keep_trailing_zeros);
debug_assert!(exp <= -1 && spec.prec + (exp + 1) >= 0);
let Ok(nsd) = usize::try_from(spec.prec + (exp + 1)) else {
fail_on_untested_path("regular_fg, sub-1 nsd overflows usize");
return None;
};
storage = mpfr_get_str_wrapper(10, nsd, p, spec);
(&storage.0, storage.1)
}
Some(d) => (&d.str, d.exp),
};
let digits = skip_sign(str, p);
if exp == 1 {
debug_assert!(digits[0] == b'1');
np.ip[0] = b'1';
if keep_trailing_zeros {
np.fp_leading_zeros = spec.prec;
}
} else {
np.fp_leading_zeros = -exp;
debug_assert!(exp <= 0);
let digits = if keep_trailing_zeros {
digits
} else {
strip_trailing_zeros(digits)
};
let str_len = digits.len();
debug_assert!(str_len > 0);
np.fp = digits.to_vec();
if keep_trailing_zeros {
np.fp_trailing_zeros = (spec.prec + exp) - i64::exact_from(str_len);
debug_assert!(np.fp_trailing_zeros >= 0);
}
}
}
}
if spec.alt || np.fp_leading_zeros != 0 || !np.fp.is_empty() || np.fp_trailing_zeros != 0 {
np.point = b'.';
}
} else {
let storage;
let (str, exp): (&[u8], i64) = match dec_info {
None => {
let exp = floor_log10(p);
debug_assert!(exp >= 0);
let n = usize::try_from(spec.prec.checked_add(exp + 1)?).ok()?;
storage = mpfr_get_str_wrapper(10, n, p, spec);
(&storage.0, storage.1)
}
Some(d) => (&d.str, d.exp),
};
let digits = skip_sign(str, p);
let str_len = digits.len();
let ip_size = if exp > i64::exact_from(str_len) {
np.ip_trailing_digits = i32::exact_from(exp - i64::exact_from(str_len));
str_len
} else {
usize::exact_from(exp)
};
np.ip = digits[..ip_size].to_vec();
if spec.group {
np.thousands_sep = b',';
}
let mut frac = &digits[ip_size..];
if !keep_trailing_zeros {
frac = strip_trailing_zeros(frac);
}
let frac_len = frac.len();
if frac_len > 0 {
np.point = b'.';
np.fp = frac.to_vec();
}
if keep_trailing_zeros && i64::exact_from(frac_len) < spec.prec {
np.point = b'.';
np.fp_trailing_zeros = spec.prec - i64::exact_from(np.fp.len());
debug_assert!(np.fp_trailing_zeros >= 0);
}
if spec.alt {
np.point = b'.';
}
}
Some(())
}
const DEFAULT_DECIMAL_PREC: i64 = 6;
fn is_like_rnda(rnd: RoundingMode, neg: bool) -> bool {
rnd == Up || (rnd == Ceiling && !neg) || (rnd == Floor && neg)
}
fn one_digit_is_inexact(sig: &Natural, nbits: u64) -> bool {
sig.trailing_zeros().unwrap() < sig.significant_bits() - nbits
}
fn next_base_power_p(x: &Float, base: i64, rnd: RoundingMode) -> bool {
let nbits: u64 = if base == 2 { 1 } else { 4 };
if rnd == Down
|| (rnd == Floor && x.is_sign_positive())
|| (rnd == Ceiling && x.is_sign_negative())
|| x.get_prec().unwrap() <= nbits
{
return false;
}
let sig = x.significand_ref().unwrap();
let xm = sig.limbs().next_back().unwrap();
let low_mask = Limb::low_mask(Limb::WIDTH - nbits);
let high_mask = !low_mask;
if (xm & high_mask) ^ high_mask != 0 {
return false;
}
if rnd == Nearest {
xm.get_bit(Limb::WIDTH - nbits - 1)
} else {
one_digit_is_inexact(sig, nbits)
}
}
fn regular_ab(np: &mut NumberParts, p: &Float, spec: &PrintfSpec) -> Option<()> {
let uppercase = spec.spec == b'A';
if matches!(spec.spec, b'a' | b'A') {
np.prefix = if uppercase { b"0X" } else { b"0x" };
}
let base: i64 = if spec.spec == b'b' { 2 } else { 16 };
let (mut digits, exp): (Vec<u8>, i64) = if spec.prec != 0 {
let (s, e) = mpfr_get_str_wrapper(base, nsd_for_prec(spec.prec)?, p, spec);
let digits = if p.is_sign_negative() {
s[1..].to_vec()
} else {
s
};
let exp = if base == 16 { (e - 1) << 2 } else { e - 1 };
(digits, exp)
} else {
let mut e = i64::from(p.get_exponent().unwrap());
let sig = p.significand_ref().unwrap();
assert!(
spec.rnd_mode != Exact || !one_digit_is_inexact(sig, if base == 2 { 1 } else { 4 }),
"regular_ab: Exact rounding was requested, but {p} is not exactly representable with \
a single base-{base} digit",
);
let digit_byte = if next_base_power_p(p, base, spec.rnd_mode) {
b'1'
} else if base == 2 {
e -= 1;
b'1'
} else {
let msl = sig.limbs().next_back().unwrap();
const RND_BIT: u64 = Limb::WIDTH - 5;
let mut digit = u8::exact_from(msl >> const { RND_BIT + 1 });
if (is_like_rnda(spec.rnd_mode, p.is_sign_negative()) && one_digit_is_inexact(sig, 4))
|| (spec.rnd_mode == Nearest && (msl & const { Limb::ONE << RND_BIT }) != 0)
{
digit += 1;
}
debug_assert!(digit <= 15);
e -= 4;
digit_to_display_byte_lower(digit).unwrap()
};
(vec![digit_byte], e)
};
if uppercase {
digits.make_ascii_uppercase();
}
np.ip = vec![digits[0]];
if spec.spec == b'b' || spec.prec != 0 {
let mut frac = &digits[1..];
if spec.prec < 0 {
frac = strip_trailing_zeros(frac);
}
let str_len = frac.len();
if str_len != 0 {
np.fp = frac.to_vec();
if spec.prec > 0 && i64::exact_from(str_len) < spec.prec {
fail_on_untested_path("regular_ab, trailing-zero pad");
np.fp_trailing_zeros = spec.prec - i64::exact_from(str_len);
}
}
}
if !np.fp.is_empty() || spec.alt {
np.point = b'.';
}
np.exp = exponent_part(if uppercase { b'P' } else { b'p' }, exp, 1);
Some(())
}
fn partition_number(p: &Float, mut spec: PrintfSpec) -> Option<(NumberParts, i64)> {
let mut np = number_parts_init();
np.pad_type = if spec.left {
PadType::Right
} else if spec.pad == b'0' {
PadType::LeadingZeros
} else {
PadType::Left
};
let uppercase = matches!(spec.spec, b'A' | b'E' | b'F' | b'G');
np.sign = if p.is_sign_negative() {
b'-'
} else if spec.showsign {
b'+'
} else if spec.space {
b' '
} else {
b'\0'
};
match p {
Float(NaN) => {
if matches!(np.pad_type, PadType::LeadingZeros) {
np.pad_type = PadType::Left;
}
np.ip = if uppercase { b"NAN" } else { b"nan" }.to_vec();
}
Float(Infinity { .. }) => {
if matches!(np.pad_type, PadType::LeadingZeros) {
np.pad_type = PadType::Left;
}
np.ip = if uppercase { b"INF" } else { b"inf" }.to_vec();
}
Float(Zero { .. }) => {
if matches!(spec.spec, b'a' | b'A') {
np.prefix = if uppercase { b"0X" } else { b"0x" };
}
np.ip = vec![b'0'];
if spec.prec < 0 {
spec.prec = match spec.spec {
b'e' | b'E' => i64::exact_from(get_str_ndigits(10, 1)) - 1,
b'f' | b'F' | b'g' | b'G' => DEFAULT_DECIMAL_PREC,
_ => spec.prec,
};
}
if spec.prec > 0 && (!matches!(spec.spec, b'g' | b'G') || spec.alt) {
np.point = b'.';
np.fp_trailing_zeros = if matches!(spec.spec, b'g' | b'G') {
spec.prec - 1
} else {
spec.prec
};
debug_assert!(np.fp_trailing_zeros >= 0);
} else if spec.alt {
np.point = b'.';
}
match spec.spec {
b'e' | b'E' => np.exp = if uppercase { b"E+00" } else { b"e+00" }.to_vec(),
b'a' | b'A' | b'b' => np.exp = if uppercase { b"P+0" } else { b"p+0" }.to_vec(),
_ => {}
}
}
Float(Finite {
exponent,
precision,
..
}) => match spec.spec {
b'a' | b'A' | b'b' => regular_ab(&mut np, p, &spec)?,
b'f' | b'F' => {
if spec.prec < 0 {
spec.prec = DEFAULT_DECIMAL_PREC;
}
regular_fg(&mut np, p, &spec, None, true)?;
}
b'e' | b'E' => regular_eg(&mut np, p, &spec, None, true)?,
_ => {
let threshold = match spec.prec {
i64::MIN..0 => DEFAULT_DECIMAL_PREC,
0 => 1,
_ => spec.prec,
};
debug_assert!(threshold >= 1);
let exp_p = i64::from(*exponent);
let k = i64::exact_from(*precision) - exp_p;
let mut e = if exp_p <= 0 {
k
} else {
(exp_p + 2) / 3 + if k <= 0 { 0 } else { k }
};
debug_assert!(e >= 1);
if e > threshold {
e = threshold;
}
let Ok(e) = usize::try_from(e) else {
fail_on_untested_path("partition_number, %g e overflows usize");
return None;
};
let (str, dec_exp, _) = get_str(p, 10, e, spec.rnd_mode).unwrap();
let dec_info = DecimalInfo { exp: dec_exp, str };
let x = dec_info.exp - 1;
if threshold > x && x >= -4 {
spec.prec = threshold.checked_sub(x)?.checked_sub(1)?;
regular_fg(&mut np, p, &spec, Some(&dec_info), spec.alt)?;
} else {
spec.prec = threshold - 1;
regular_eg(&mut np, p, &spec, Some(&dec_info), spec.alt)?;
}
}
},
}
let mut total: i128 = i128::from(np.sign != b'\0');
total += np.prefix.len() as i128;
total += np.ip.len() as i128;
total += i128::from(np.ip_trailing_digits);
debug_assert!(np.ip.len() as i128 + i128::from(np.ip_trailing_digits) >= 1);
if np.thousands_sep != b'\0' {
total += (np.ip.len() as i128 + i128::from(np.ip_trailing_digits) - 1) / 3;
}
if np.point != b'\0' {
total += 1;
}
total += i128::from(np.fp_leading_zeros);
total += np.fp.len() as i128;
total += i128::from(np.fp_trailing_zeros);
total += np.exp.len() as i128;
if i128::from(spec.width) > total {
np.pad_size = spec.width - i64::exact_from(total);
total = i128::from(spec.width);
}
if total > i128::from(i64::MAX) {
fail_on_untested_path("partition_number, total width overflows i64");
return None;
}
Some((np, i64::exact_from(total)))
}
fn sprnt_fp(buf: &mut Vec<u8>, p: &Float, spec: &PrintfSpec) -> Option<()> {
let (np, length) = partition_number(p, *spec)?;
buf.reserve(usize::try_from(length).unwrap_or(0));
if matches!(np.pad_type, PadType::Left) {
buffer_pad(buf, b' ', np.pad_size);
}
if np.sign != b'\0' {
buf.push(np.sign);
}
buf.extend_from_slice(np.prefix);
if matches!(np.pad_type, PadType::LeadingZeros) {
buffer_pad(buf, b'0', np.pad_size);
}
if np.thousands_sep != b'\0' {
buffer_sandwich(
buf,
&np.ip,
usize::exact_from(np.ip_trailing_digits),
np.thousands_sep,
);
} else {
buf.extend_from_slice(&np.ip);
debug_assert!(np.ip_trailing_digits <= 1);
if np.ip_trailing_digits != 0 {
buf.push(b'0');
}
}
if np.point != b'\0' {
buf.push(np.point);
}
buffer_pad(buf, b'0', np.fp_leading_zeros);
buf.extend_from_slice(&np.fp);
buffer_pad(buf, b'0', np.fp_trailing_zeros);
buf.extend_from_slice(&np.exp);
if matches!(np.pad_type, PadType::Right) {
buffer_pad(buf, b' ', np.pad_size);
}
Some(())
}
pub_const_crate_test! {float_conversion_spec(
conv: u8,
prec: i64,
width: i64,
rm: RoundingMode,
) -> PrintfSpec {
let mut spec = specinfo_init();
spec.spec = conv;
spec.prec = prec;
spec.width = width;
spec.rnd_mode = rm;
spec
}}
pub_crate_test! {format_float(p: &Float, spec: &PrintfSpec) -> Option<String> {
let mut buf = Vec::new();
sprnt_fp(&mut buf, p, spec)?;
Some(String::from_utf8(buf).unwrap())
}}
pub_crate_test_enum! {
PrintfArg<'a> {
Float(&'a Float),
#[allow(dead_code)]
Int(i64),
#[allow(dead_code)]
Str(&'a str),
}}
const fn is_float_conversion(c: u8) -> bool {
matches!(
c,
b'a' | b'A' | b'b' | b'e' | b'E' | b'f' | b'F' | b'g' | b'G'
)
}
fn read_int<'a>(
mut fmt: &'a [u8],
args: &mut core::slice::Iter<PrintfArg>,
) -> (Option<i64>, &'a [u8]) {
if let Some((&b'*', tail)) = fmt.split_first() {
let n = match args.next() {
Some(PrintfArg::Int(n)) => *n,
_ => 0,
};
(Some(n), tail)
} else {
let mut n: Option<i64> = Some(0);
while let Some((&d, tail)) = fmt.split_first()
&& d.is_ascii_digit()
{
n = n
.and_then(|n| n.checked_mul(10))
.and_then(|n| n.checked_add(i64::from(d - b'0')));
fmt = tail;
}
(n, fmt)
}
}
fn pad_to_width(
out: &mut Vec<u8>,
sign: Option<u8>,
body: &[u8],
spec: &PrintfSpec,
zero_ok: bool,
) {
let core_len = body.len() + usize::from(sign.is_some());
let width = usize::try_from(spec.width).unwrap_or(0);
let pad = width.saturating_sub(core_len);
if spec.left {
if let Some(s) = sign {
out.push(s);
}
out.extend_from_slice(body);
out.resize(out.len() + pad, b' ');
} else if zero_ok && spec.pad == b'0' && spec.prec < 0 {
if let Some(s) = sign {
out.push(s);
}
out.resize(out.len() + pad, b'0');
out.extend_from_slice(body);
} else {
out.resize(out.len() + pad, b' ');
if let Some(s) = sign {
out.push(s);
}
out.extend_from_slice(body);
}
}
fn format_int(n: i64, spec: &PrintfSpec) -> Vec<u8> {
let neg = n < 0;
let mag = n.unsigned_abs();
let mut digits = format!("{mag}").into_bytes();
if spec.prec >= 0 {
if spec.prec == 0 && mag == 0 {
digits.clear();
} else if let Ok(want) = usize::try_from(spec.prec)
&& digits.len() < want
{
let mut d = vec![b'0'; want - digits.len()];
d.extend_from_slice(&digits);
digits = d;
}
}
if spec.group && digits.len() > 3 {
let len = digits.len();
let mut grouped = Vec::with_capacity(len + (len - 1) / 3);
let r = (len - 1) % 3 + 1;
grouped.extend_from_slice(&digits[..r]);
for chunk in digits[r..].chunks(3) {
grouped.push(b',');
grouped.extend_from_slice(chunk);
}
digits = grouped;
}
let sign = if neg {
Some(b'-')
} else if spec.showsign {
Some(b'+')
} else if spec.space {
Some(b' ')
} else {
None
};
let mut out = Vec::new();
pad_to_width(&mut out, sign, &digits, spec, true);
out
}
fn format_str(s: &str, spec: &PrintfSpec) -> Vec<u8> {
let s = if spec.prec >= 0 {
let mut n = usize::try_from(spec.prec)
.unwrap_or(usize::MAX)
.min(s.len());
while !s.is_char_boundary(n) {
n -= 1;
}
&s[..n]
} else {
s
};
let mut out = Vec::new();
pad_to_width(&mut out, None, s.as_bytes(), spec, false);
out
}
pub_crate_test! {format(fmt: &[u8], args: &[PrintfArg]) -> Option<Vec<u8>> {
let mut out = Vec::new();
let mut fmt = fmt;
let mut args = args.iter();
while let Some(&c) = fmt.first() {
if c != b'%' {
out.push(c);
fmt = &fmt[1..];
continue;
}
fmt = &fmt[1..];
if fmt.first() == Some(&b'%') {
out.push(b'%');
fmt = &fmt[1..];
continue;
}
let mut spec = specinfo_init();
fmt = parse_flags(fmt, &mut spec);
let (w, rest) = read_int(fmt, &mut args);
fmt = rest;
spec.width = w?;
if spec.width < 0 {
spec.left = true;
spec.width = spec.width.saturating_neg();
}
spec.prec = if fmt.first() == Some(&b'.') {
fmt = &fmt[1..];
let (pr, rest) = read_int(fmt, &mut args);
fmt = rest;
let pr = pr?;
if pr < 0 { -1 } else { pr }
} else {
-1
};
fmt = parse_arg_type(fmt, &mut spec);
if spec.arg_type == ArgType::Mpfr
&& let Some((&c, tail)) = fmt.split_first()
{
let rm = match c {
b'D' => Some(Floor),
b'U' => Some(Ceiling),
b'Y' => Some(Up),
b'Z' => Some(Down),
b'N' => Some(Nearest),
b'*' => Some(match args.next() {
Some(PrintfArg::Int(1)) => Down,
Some(PrintfArg::Int(2)) => Ceiling,
Some(PrintfArg::Int(3)) => Floor,
Some(PrintfArg::Int(4)) => Up,
_ => Nearest,
}),
_ => None,
};
if let Some(rm) = rm {
spec.rnd_mode = rm;
fmt = tail;
}
}
let Some((&conversion, tail)) = fmt.split_first() else {
break;
};
spec.spec = conversion;
fmt = tail;
if !specinfo_is_valid(spec) {
continue;
}
match (spec.spec, spec.arg_type) {
(c, ArgType::Mpfr) if is_float_conversion(c) => {
let PrintfArg::Float(p) = args.next()? else {
return None;
};
sprnt_fp(&mut out, p, &spec)?;
}
(
b'd' | b'i',
ArgType::None
| ArgType::Char
| ArgType::Short
| ArgType::Long
| ArgType::LongLong
| ArgType::IntMax
| ArgType::Size
| ArgType::PtrDiff,
) => {
let PrintfArg::Int(n) = args.next()? else {
return None;
};
out.extend_from_slice(&format_int(*n, &spec));
}
(b's', ArgType::None) => {
let PrintfArg::Str(s) = args.next()? else {
return None;
};
out.extend_from_slice(&format_str(s, &spec));
}
_ => return None,
}
}
Some(out)
}}
#[inline]
pub fn format_float_str(x: &Float, fmt: &str) -> Option<String> {
format(fmt.as_bytes(), &[PrintfArg::Float(x)]).map(|v| String::from_utf8(v).unwrap())
}