use super::arg::Arg;
use super::fmt_fp::format_float;
use super::locale::Locale;
use std::fmt::{self, Write};
use std::mem;
use std::result::Result;
#[cfg(feature = "widestring")]
use widestring::Utf32Str as wstr;
#[derive(Debug, PartialEq, Eq)]
pub enum Error {
BadFormatString,
MissingArg,
ExtraArg,
BadArgType,
Overflow,
Fmt(fmt::Error),
}
impl From<fmt::Error> for Error {
fn from(err: fmt::Error) -> Error {
Error::Fmt(err)
}
}
#[derive(Debug, Copy, Clone, Default)]
pub(super) struct ModifierFlags {
pub alt_form: bool, pub zero_pad: bool, pub left_adj: bool, pub pad_pos: bool, pub mark_pos: bool, pub grouped: bool, }
impl ModifierFlags {
fn try_set(&mut self, c: char) -> bool {
match c {
'#' => self.alt_form = true,
'0' => self.zero_pad = true,
'-' => self.left_adj = true,
' ' => self.pad_pos = true,
'+' => self.mark_pos = true,
'\'' => self.grouped = true,
_ => return false,
};
true
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[allow(non_camel_case_types)]
enum ConversionPrefix {
Empty,
hh,
h,
l,
ll,
j,
t,
z,
L,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[allow(non_camel_case_types)]
#[rustfmt::skip]
pub(super) enum ConversionSpec {
d, o, u, x, X,
n,
a, A, e, E, f, F, g, G,
p,
c, s,
}
impl ConversionSpec {
fn supports_prefix(self, prefix: ConversionPrefix) -> bool {
use ConversionPrefix::*;
use ConversionSpec::*;
if matches!(prefix, Empty) {
return true;
}
match self {
d | o | u | x | X | n => matches!(prefix, hh | h | l | ll | j | t | z),
a | A | e | E | f | F | g | G => matches!(prefix, l | L),
p => false,
c | s => matches!(prefix, l),
}
}
#[inline]
pub(super) fn is_lower(self) -> bool {
use ConversionSpec::*;
match self {
d | o | u | x | n | a | e | f | g | p | c | s => true,
X | A | E | F | G => false,
}
}
fn from_char(cc: char) -> Option<Self> {
use ConversionSpec::*;
let res = match cc {
'd' | 'i' => d,
'o' => o,
'u' => u,
'x' => x,
'X' => X,
'n' => n,
'a' => a,
'A' => A,
'e' => e,
'E' => E,
'f' => f,
'F' => F,
'g' => g,
'G' => G,
'p' => p,
'c' | 'C' => c,
's' | 'S' => s,
_ => return None,
};
Some(res)
}
}
pub trait FormatString {
fn is_empty(&self) -> bool;
fn at(&self, index: usize) -> Option<char>;
fn advance_by(&mut self, n: usize);
fn take_literal<'a: 'b, 'b>(&'a mut self, buffer: &'b mut String) -> &'b str;
}
impl FormatString for &str {
fn is_empty(&self) -> bool {
(*self).is_empty()
}
fn at(&self, index: usize) -> Option<char> {
self.chars().nth(index)
}
fn advance_by(&mut self, n: usize) {
let mut chars = self.chars();
for _ in 0..n {
let c = chars.next();
assert!(c.is_some(), "FormatString::advance(): index out of bounds");
}
*self = chars.as_str();
}
fn take_literal<'a: 'b, 'b>(&'a mut self, _buffer: &'b mut String) -> &'b str {
let non_percents: usize = self
.chars()
.take_while(|&c| c != '%')
.map(|c| c.len_utf8())
.sum();
let percent_pairs = self[non_percents..]
.chars()
.take_while(|&c| c == '%')
.count()
/ 2;
let (prefix, rest) = self.split_at(non_percents + percent_pairs * 2);
*self = rest;
&prefix[..prefix.len() - percent_pairs]
}
}
#[cfg(feature = "widestring")]
impl FormatString for &wstr {
fn is_empty(&self) -> bool {
(*self).is_empty()
}
fn at(&self, index: usize) -> Option<char> {
self.as_char_slice().get(index).copied()
}
fn advance_by(&mut self, n: usize) {
*self = &self[n..];
}
fn take_literal<'a: 'b, 'b>(&'a mut self, buffer: &'b mut String) -> &'b str {
let s = self.as_char_slice();
let non_percents = s.iter().take_while(|&&c| c != '%').count();
let percent_pairs: usize = s[non_percents..].iter().take_while(|&&c| c == '%').count() / 2;
*self = &self[non_percents + percent_pairs * 2..];
buffer.clear();
buffer.extend(s[..non_percents + percent_pairs].iter());
buffer.as_str()
}
}
fn get_int(fmt: &mut impl FormatString) -> Result<usize, Error> {
use Error::Overflow;
let mut i: usize = 0;
while let Some(digit) = fmt.at(0).and_then(|c| c.to_digit(10)) {
i = i.checked_mul(10).ok_or(Overflow)?;
i = i.checked_add(digit as usize).ok_or(Overflow)?;
fmt.advance_by(1);
}
Ok(i)
}
fn get_prefix(fmt: &mut impl FormatString) -> ConversionPrefix {
use ConversionPrefix as CP;
let prefix = match fmt.at(0).unwrap_or('\0') {
'h' if fmt.at(1) == Some('h') => CP::hh,
'h' => CP::h,
'l' if fmt.at(1) == Some('l') => CP::ll,
'l' => CP::l,
'j' => CP::j,
't' => CP::t,
'z' => CP::z,
'L' => CP::L,
_ => CP::Empty,
};
fmt.advance_by(match prefix {
CP::Empty => 0,
CP::hh | CP::ll => 2,
_ => 1,
});
prefix
}
fn get_specifier(fmt: &mut impl FormatString) -> Result<ConversionSpec, Error> {
let prefix = get_prefix(fmt);
if prefix != ConversionPrefix::Empty && matches!(fmt.at(0), Some('C' | 'S')) {
return Err(Error::BadFormatString);
}
let spec = fmt
.at(0)
.and_then(ConversionSpec::from_char)
.ok_or(Error::BadFormatString)?;
if !spec.supports_prefix(prefix) {
return Err(Error::BadFormatString);
}
fmt.advance_by(1);
Ok(spec)
}
pub(super) fn pad(
f: &mut impl Write,
c: char,
min_width: usize,
current_width: usize,
) -> fmt::Result {
assert!(c == '0' || c == ' ');
if current_width >= min_width {
return Ok(());
}
const ZEROS: &str = "0000000000000000";
const SPACES: &str = " ";
let buff = if c == '0' { ZEROS } else { SPACES };
let mut remaining = min_width - current_width;
while remaining > 0 {
let n = remaining.min(buff.len());
f.write_str(&buff[..n])?;
remaining -= n;
}
Ok(())
}
pub fn sprintf_locale(
f: &mut impl Write,
fmt: impl FormatString,
locale: &Locale,
args: &mut [Arg],
) -> Result<usize, Error> {
use ConversionSpec as CS;
let mut s = fmt;
let mut args = args.iter_mut();
let mut out_len: usize = 0;
let buf = &mut String::new();
'main: while !s.is_empty() {
buf.clear();
let lit = s.take_literal(buf);
if !lit.is_empty() {
f.write_str(lit)?;
out_len = out_len
.checked_add(lit.chars().count())
.ok_or(Error::Overflow)?;
continue 'main;
}
debug_assert!(s.at(0) == Some('%'));
s.advance_by(1);
let mut flags = ModifierFlags::default();
while flags.try_set(s.at(0).unwrap_or('\0')) {
s.advance_by(1);
}
if flags.left_adj {
flags.zero_pad = false;
}
let width = if s.at(0) == Some('*') {
let arg_width = args.next().ok_or(Error::MissingArg)?.as_sint()?;
s.advance_by(1);
if arg_width < 0 {
flags.left_adj = true;
}
arg_width
.unsigned_abs()
.try_into()
.map_err(|_| Error::Overflow)?
} else {
get_int(&mut s)?
};
let mut prec: Option<usize> = if s.at(0) == Some('.') && s.at(1) == Some('*') {
s.advance_by(2);
let p = args.next().ok_or(Error::MissingArg)?.as_sint()?;
p.try_into().ok()
} else if s.at(0) == Some('.') {
s.advance_by(1);
Some(get_int(&mut s)?)
} else {
None
};
if prec.unwrap_or(0) > i32::MAX as usize {
return Err(Error::Overflow);
}
let conv_spec = get_specifier(&mut s)?;
let arg = args.next().ok_or(Error::MissingArg)?;
let mut prefix = "";
if flags.grouped && !matches!(conv_spec, CS::d | CS::u | CS::f | CS::F) {
return Err(Error::BadFormatString);
}
let spec_is_numeric = matches!(conv_spec, CS::d | CS::u | CS::o | CS::p | CS::x | CS::X);
if spec_is_numeric && prec.is_some() {
flags.zero_pad = false;
}
let body: &str = match conv_spec {
CS::n => {
arg.set_count(out_len)?;
continue 'main;
}
CS::e | CS::f | CS::g | CS::a | CS::E | CS::F | CS::G | CS::A => {
let float = arg.as_float()?;
let len = format_float(f, float, width, prec, flags, locale, conv_spec, buf)?;
out_len = out_len.checked_add(len).ok_or(Error::Overflow)?;
continue 'main;
}
CS::p => {
const PTR_HEX_DIGITS: usize = 2 * mem::size_of::<*const u8>();
prec = prec.map(|p| p.max(PTR_HEX_DIGITS));
let uint = arg.as_uint()?;
if uint != 0 {
prefix = "0x";
write!(buf, "{:x}", uint)?;
}
buf
}
CS::x | CS::X => {
let lower = conv_spec.is_lower();
let (_, uint) = arg.as_wrapping_sint()?;
if uint != 0 {
if flags.alt_form {
prefix = if lower { "0x" } else { "0X" };
}
if lower {
write!(buf, "{:x}", uint)?;
} else {
write!(buf, "{:X}", uint)?;
}
}
buf
}
CS::o => {
let uint = arg.as_uint()?;
if uint != 0 {
write!(buf, "{:o}", uint)?;
}
if flags.alt_form && prec.unwrap_or(0) <= buf.len() + 1 {
prec = Some(buf.len() + 1);
}
buf
}
CS::u => {
let uint = arg.as_uint()?;
if uint != 0 {
write!(buf, "{}", uint)?;
}
buf
}
CS::d => {
let arg_i = arg.as_sint()?;
if arg_i < 0 {
prefix = "-";
} else if flags.mark_pos {
prefix = "+";
} else if flags.pad_pos {
prefix = " ";
}
if arg_i != 0 {
write!(buf, "{}", arg_i.unsigned_abs())?;
}
buf
}
CS::c => {
flags.zero_pad = false;
buf.push(arg.as_char()?);
buf
}
CS::s => {
let s = arg.as_str(buf)?;
let p = prec.unwrap_or(s.len()).min(s.len());
prec = Some(p);
flags.zero_pad = false;
&s[..p]
}
};
if spec_is_numeric && body.is_empty() {
debug_assert!(arg.as_uint().unwrap() == 0);
}
let wants_grouping = flags.grouped && locale.thousands_sep.is_some();
let body_len = match wants_grouping {
true => body.len() + locale.separator_count(body.len()),
false => body.len(),
};
let prec = if !spec_is_numeric {
prec.unwrap_or(body_len)
} else {
prec.unwrap_or(1).max(body_len)
};
let prefix_len = prefix.len();
let unpadded_width = prefix_len.checked_add(prec).ok_or(Error::Overflow)?;
let width = width.max(unpadded_width);
if !flags.left_adj && !flags.zero_pad {
pad(f, ' ', width, unpadded_width)?;
}
f.write_str(prefix)?;
if !flags.left_adj && flags.zero_pad {
pad(f, '0', width, unpadded_width)?;
}
pad(f, '0', prec, body_len)?;
if wants_grouping {
f.write_str(&locale.apply_grouping(body))?;
} else {
f.write_str(body)?;
}
if flags.left_adj {
pad(f, ' ', width, unpadded_width)?;
}
out_len = out_len.checked_add(width).ok_or(Error::Overflow)?;
}
if args.next().is_some() {
return Err(Error::ExtraArg);
}
Ok(out_len)
}