use super::arg::Arg;
use super::fmt_fp::format_float;
use super::locale::Locale;
use bstr::{BStr, ByteSlice as _};
use std::io::{self, Write as IoWrite};
use std::mem;
use std::result::Result;
#[derive(Debug, PartialEq, Eq)]
pub enum Error {
BadFormatString,
MissingArg,
BadArgType,
Overflow,
Io(io::ErrorKind),
}
impl From<io::Error> for Error {
fn from(err: io::Error) -> Error {
Error::Io(err.kind())
}
}
#[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: u8) -> bool {
match c {
b'#' => self.alt_form = true,
b'0' => self.zero_pad = true,
b'-' => self.left_adj = true,
b' ' => self.pad_pos = true,
b'+' => self.mark_pos = true,
b'\'' => 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 => false,
}
}
#[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_byte(cc: u8) -> Option<Self> {
use ConversionSpec::*;
let res = match cc {
b'd' | b'i' => d,
b'o' => o,
b'u' => u,
b'x' => x,
b'X' => X,
b'n' => n,
b'a' => a,
b'A' => A,
b'e' => e,
b'E' => E,
b'f' => f,
b'F' => F,
b'g' => g,
b'G' => G,
b'p' => p,
b'c' => c,
b's' => s,
_ => return None,
};
Some(res)
}
}
trait FormatString<'a> {
fn is_empty(&self) -> bool;
fn at(&self, index: usize) -> Option<u8>;
fn advance_by(&mut self, n: usize);
fn take_literal(&mut self) -> &'a BStr;
}
impl<'a> FormatString<'a> for &'a BStr {
fn is_empty(&self) -> bool {
self.len() == 0
}
fn at(&self, index: usize) -> Option<u8> {
self.get(index).copied()
}
fn advance_by(&mut self, n: usize) {
debug_assert!(
n <= self.len(),
"FormatString::advance_by(): index out of bounds"
);
*self = self[n..].as_bstr();
}
fn take_literal(&mut self) -> &'a BStr {
let non_percents: usize = self.iter().take_while(|&&c| c != b'%').count();
let percent_pairs = self[non_percents..]
.iter()
.take_while(|&&c| c == b'%')
.count()
/ 2;
let (prefix, rest) = self.split_at(non_percents + percent_pairs * 2);
*self = rest.as_bstr();
prefix[..prefix.len() - percent_pairs].as_bstr()
}
}
fn get_int<'a>(fmt: &mut impl FormatString<'a>) -> Result<usize, Error> {
use Error::Overflow;
let mut i: usize = 0;
while let Some(digit) = fmt.at(0).and_then(|c| {
if c.is_ascii_digit() {
Some(c - b'0')
} else {
None
}
}) {
i = i.checked_mul(10).ok_or(Overflow)?;
i = i.checked_add(usize::from(digit)).ok_or(Overflow)?;
fmt.advance_by(1);
}
Ok(i)
}
fn get_prefix<'a>(fmt: &mut impl FormatString<'a>) -> ConversionPrefix {
use ConversionPrefix as CP;
let prefix = match fmt.at(0).unwrap_or(b'\0') {
b'h' if fmt.at(1) == Some(b'h') => CP::hh,
b'h' => CP::h,
b'l' if fmt.at(1) == Some(b'l') => CP::ll,
b'l' => CP::l,
b'j' => CP::j,
b't' => CP::t,
b'z' => CP::z,
b'L' => CP::L,
_ => CP::Empty,
};
fmt.advance_by(match prefix {
CP::Empty => 0,
CP::hh | CP::ll => 2,
_ => 1,
});
prefix
}
fn get_specifier<'a>(fmt: &mut impl FormatString<'a>) -> Result<ConversionSpec, Error> {
let prefix = get_prefix(fmt);
let spec = fmt
.at(0)
.and_then(ConversionSpec::from_byte)
.ok_or(Error::BadFormatString)?;
if !spec.supports_prefix(prefix) {
return Err(Error::BadFormatString);
}
fmt.advance_by(1);
Ok(spec)
}
fn c_string_prefix(fmt: &BStr) -> &BStr {
let len = fmt.iter().position(|&c| c == b'\0').unwrap_or(fmt.len());
fmt[..len].as_bstr()
}
fn check_printf_count(count: usize) -> Result<usize, Error> {
if count > i32::MAX as usize {
return Err(Error::Overflow);
}
Ok(count)
}
fn add_printf_count(count: usize, add: usize) -> Result<usize, Error> {
check_printf_count(count.checked_add(add).ok_or(Error::Overflow)?)
}
pub(crate) trait FormatSink {
fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), Error>;
fn write_repeat(&mut self, byte: u8, count: usize) -> Result<(), Error> {
assert!(matches!(byte, b'0' | b' '));
const ZEROS: &[u8] = b"0000000000000000";
const SPACES: &[u8] = b" ";
let bytes = if byte == b'0' { ZEROS } else { SPACES };
let mut remaining = count;
while remaining > 0 {
let size = remaining.min(bytes.len());
self.write_bytes(&bytes[..size])?;
remaining -= size;
}
Ok(())
}
}
struct IoSink<'a, W: IoWrite + ?Sized> {
output: &'a mut W,
}
impl<W: IoWrite + ?Sized> FormatSink for IoSink<'_, W> {
fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), Error> {
self.output.write_all(bytes)?;
Ok(())
}
}
struct SliceSink<'a> {
buffer: &'a mut [u8],
len: usize,
}
impl FormatSink for SliceSink<'_> {
fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), Error> {
let remaining = self.buffer.len().saturating_sub(self.len);
let stored = remaining.min(bytes.len());
if stored != 0 {
self.buffer[self.len..self.len + stored].copy_from_slice(&bytes[..stored]);
self.len += stored;
}
Ok(())
}
fn write_repeat(&mut self, byte: u8, count: usize) -> Result<(), Error> {
assert!(matches!(byte, b'0' | b' '));
let remaining = self.buffer.len().saturating_sub(self.len);
let stored = remaining.min(count);
if stored != 0 {
self.buffer[self.len..self.len + stored].fill(byte);
self.len += stored;
}
Ok(())
}
}
pub fn printf_locale_to_slice(
buffer: &mut [u8],
fmt: &BStr,
locale: &Locale,
args: &mut [Arg],
) -> Result<usize, Error> {
let mut sink = SliceSink { buffer, len: 0 };
format_locale(&mut sink, fmt, locale, args)
}
pub(super) fn pad(
f: &mut (impl FormatSink + ?Sized),
c: u8,
min_width: usize,
current_width: usize,
) -> Result<(), Error> {
assert!(matches!(c, b'0' | b' '));
if current_width >= min_width {
return Ok(());
}
f.write_repeat(c, min_width - current_width)
}
fn format_unsigned_digits(
storage: &mut [u8; 64],
mut value: u64,
radix: u64,
uppercase: bool,
) -> &[u8] {
debug_assert!(matches!(radix, 8 | 10 | 16));
debug_assert_ne!(value, 0);
let digits = if uppercase {
b"0123456789ABCDEF"
} else {
b"0123456789abcdef"
};
let mut index = storage.len();
while value != 0 {
index -= 1;
storage[index] = digits[(value % radix) as usize];
value /= radix;
}
&storage[index..]
}
pub fn sprintf_locale<W: IoWrite + ?Sized>(
f: &mut W,
fmt: &BStr,
locale: &Locale,
args: &mut [Arg],
) -> Result<usize, Error> {
let mut sink = IoSink { output: f };
format_locale(&mut sink, fmt, locale, args)
}
fn format_locale(
f: &mut (impl FormatSink + ?Sized),
fmt: &BStr,
locale: &Locale,
args: &mut [Arg],
) -> Result<usize, Error> {
use ConversionSpec as CS;
let mut s = c_string_prefix(fmt);
let mut args = args.iter_mut();
let mut out_len: usize = 0;
let mut float_buf = None;
'main: while !s.is_empty() {
let lit = s.take_literal();
if !lit.is_empty() {
f.write_bytes(lit.as_ref())?;
out_len = add_printf_count(out_len, lit.len())?;
continue 'main;
}
debug_assert_eq!(s.at(0), Some(b'%'));
s.advance_by(1);
let mut flags = ModifierFlags::default();
while flags.try_set(s.at(0).unwrap_or(b'\0')) {
s.advance_by(1);
}
if flags.left_adj {
flags.zero_pad = false;
}
let desired_width = if s.at(0) == Some(b'*') {
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)?
};
check_printf_count(desired_width)?;
let mut desired_precision: Option<usize> = if s.at(0) == Some(b'.') && s.at(1) == Some(b'*')
{
s.advance_by(2);
let p = args.next().ok_or(Error::MissingArg)?.as_sint()?;
p.try_into().ok()
} else if s.at(0) == Some(b'.') {
s.advance_by(1);
Some(get_int(&mut s)?)
} else {
None
};
if let Some(precision) = desired_precision {
check_printf_count(precision)?;
}
let conv_spec = get_specifier(&mut s)?;
let arg = args.next().ok_or(Error::MissingArg)?;
let mut prefix = b"".as_slice();
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 && desired_precision.is_some() {
flags.zero_pad = false;
}
let mut body_storage = [0u8; 64];
let body = 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 buf = float_buf.get_or_insert_with(|| Vec::with_capacity(64));
buf.clear();
let len = format_float(
f,
float,
desired_width,
desired_precision,
flags,
locale,
conv_spec,
buf,
)?;
out_len = add_printf_count(out_len, len)?;
continue 'main;
}
CS::p => {
const PTR_HEX_DIGITS: usize = 2 * mem::size_of::<*const u8>();
desired_precision = desired_precision.map(|p| p.max(PTR_HEX_DIGITS));
let uint = arg.as_uint()?;
if uint == 0 {
&[][..]
} else {
prefix = b"0x";
format_unsigned_digits(&mut body_storage, uint, 16, false)
}
}
CS::x | CS::X => {
let lower = conv_spec.is_lower();
let uint = arg.as_wrapping_sint()?;
if uint == 0 {
&[][..]
} else {
if flags.alt_form {
prefix = if lower { b"0x" } else { b"0X" };
}
format_unsigned_digits(&mut body_storage, uint, 16, !lower)
}
}
CS::o => {
let uint = arg.as_uint()?;
let body = if uint == 0 {
&[][..]
} else {
format_unsigned_digits(&mut body_storage, uint, 8, false)
};
if flags.alt_form && desired_precision.unwrap_or(0) <= body.len() + 1 {
desired_precision = Some(body.len() + 1);
}
body
}
CS::u => {
let uint = arg.as_uint()?;
if uint == 0 {
&[][..]
} else {
format_unsigned_digits(&mut body_storage, uint, 10, false)
}
}
CS::d => {
let arg_i = arg.as_sint()?;
if arg_i < 0 {
prefix = b"-";
} else if flags.mark_pos {
prefix = b"+";
} else if flags.pad_pos {
prefix = b" ";
}
if arg_i == 0 {
&[][..]
} else {
format_unsigned_digits(&mut body_storage, arg_i.unsigned_abs(), 10, false)
}
}
CS::c => {
flags.zero_pad = false;
body_storage[0] = arg.as_uchar()?;
&body_storage[..1]
}
CS::s => {
let s = arg.as_bstr()?;
flags.zero_pad = false;
let scan_limit =
desired_precision.map_or(s.len(), |precision| precision.min(s.len()));
let len = s[..scan_limit]
.iter()
.position(|&c| c == b'\0')
.unwrap_or(scan_limit);
desired_precision = Some(len);
&s[..len]
}
};
if spec_is_numeric && body.is_empty() {
debug_assert_eq!(arg.as_uint().unwrap(), 0);
}
let wants_grouping = flags.grouped && locale.thousands_sep.is_some();
let body_width = match wants_grouping {
true => body.len() + locale.separator_count(body.len()),
false => body.len(),
};
let desired_precision = if !spec_is_numeric {
desired_precision.unwrap_or(body_width)
} else {
desired_precision.unwrap_or(1).max(body_width)
};
let prefix_width = prefix.len();
let unpadded_width = prefix_width
.checked_add(desired_precision)
.ok_or(Error::Overflow)?;
let width = desired_width.max(unpadded_width);
if !flags.left_adj && !flags.zero_pad {
pad(f, b' ', width, unpadded_width)?;
}
f.write_bytes(prefix)?;
if !flags.left_adj && flags.zero_pad {
pad(f, b'0', width, unpadded_width)?;
}
pad(f, b'0', desired_precision, body_width)?;
if wants_grouping {
f.write_bytes(&locale.apply_grouping(body))?;
} else {
f.write_bytes(body)?;
}
if flags.left_adj {
pad(f, b' ', width, unpadded_width)?;
}
out_len = add_printf_count(out_len, width)?;
}
Ok(out_len)
}