use crate::{
error::{err, Error},
fmt::Parsed,
util::{escape, parse, t},
};
#[derive(Clone, Copy, Debug)]
pub(crate) struct DecimalFormatter {
force_sign: Option<bool>,
minimum_digits: u8,
padding_byte: u8,
}
impl DecimalFormatter {
pub(crate) const fn new() -> DecimalFormatter {
DecimalFormatter {
force_sign: None,
minimum_digits: 0,
padding_byte: b'0',
}
}
#[cfg(test)]
pub(crate) const fn format(&self, value: i64) -> Decimal {
Decimal::new(self, value)
}
#[cfg(test)]
pub(crate) const fn force_sign(
self,
zero_is_positive: bool,
) -> DecimalFormatter {
DecimalFormatter { force_sign: Some(zero_is_positive), ..self }
}
pub(crate) const fn padding(self, mut digits: u8) -> DecimalFormatter {
if digits > Decimal::MAX_I64_DIGITS {
digits = Decimal::MAX_I64_DIGITS;
}
DecimalFormatter { minimum_digits: digits, ..self }
}
pub(crate) const fn padding_byte(self, byte: u8) -> DecimalFormatter {
DecimalFormatter { padding_byte: byte, ..self }
}
}
impl Default for DecimalFormatter {
fn default() -> DecimalFormatter {
DecimalFormatter::new()
}
}
#[derive(Debug)]
pub(crate) struct Decimal {
buf: [u8; Self::MAX_I64_LEN as usize],
start: u8,
end: u8,
}
impl Decimal {
const MAX_I64_LEN: u8 = 20;
const MAX_I64_DIGITS: u8 = 19;
pub(crate) const fn new(
formatter: &DecimalFormatter,
value: i64,
) -> Decimal {
let sign = value.signum();
let Some(mut value) = value.checked_abs() else {
let buf = [
b'-', b'9', b'2', b'2', b'3', b'3', b'7', b'2', b'0', b'3',
b'6', b'8', b'5', b'4', b'7', b'7', b'5', b'8', b'0', b'8',
];
return Decimal { buf, start: 0, end: Self::MAX_I64_LEN };
};
let mut decimal = Decimal {
buf: [0; Self::MAX_I64_LEN as usize],
start: Self::MAX_I64_LEN,
end: Self::MAX_I64_LEN,
};
loop {
decimal.start -= 1;
let digit = (value % 10) as u8;
value /= 10;
decimal.buf[decimal.start as usize] = b'0' + digit;
if value == 0 {
break;
}
}
while decimal.len() < formatter.minimum_digits {
decimal.start -= 1;
decimal.buf[decimal.start as usize] = formatter.padding_byte;
}
if sign < 0 {
decimal.start -= 1;
decimal.buf[decimal.start as usize] = b'-';
} else if let Some(zero_is_positive) = formatter.force_sign {
let ascii_sign =
if sign > 0 || zero_is_positive { b'+' } else { b'-' };
decimal.start -= 1;
decimal.buf[decimal.start as usize] = ascii_sign;
}
decimal
}
const fn len(&self) -> u8 {
self.end - self.start
}
pub(crate) fn as_bytes(&self) -> &[u8] {
&self.buf[usize::from(self.start)..usize::from(self.end)]
}
pub(crate) fn as_str(&self) -> &str {
unsafe { core::str::from_utf8_unchecked(self.as_bytes()) }
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct FractionalFormatter {
precision: Option<u8>,
}
impl FractionalFormatter {
pub(crate) const fn new() -> FractionalFormatter {
FractionalFormatter { precision: None }
}
pub(crate) const fn format(&self, value: i64) -> Fractional {
Fractional::new(self, value)
}
pub(crate) const fn precision(
self,
precision: Option<u8>,
) -> FractionalFormatter {
let precision = match precision {
None => None,
Some(p) if p > 9 => Some(9),
Some(p) => Some(p),
};
FractionalFormatter { precision, ..self }
}
}
#[derive(Debug)]
pub(crate) struct Fractional {
buf: [u8; Self::MAX_LEN as usize],
end: u8,
}
impl Fractional {
const MAX_LEN: u8 = 9;
pub(crate) const fn new(
formatter: &FractionalFormatter,
mut value: i64,
) -> Fractional {
assert!(0 <= value && value <= 999_999_999);
let mut fractional = Fractional {
buf: [b'0'; Self::MAX_LEN as usize],
end: Self::MAX_LEN,
};
let mut i = 9;
loop {
i -= 1;
let digit = (value % 10) as u8;
value /= 10;
fractional.buf[i] += digit;
if value == 0 {
break;
}
}
if let Some(precision) = formatter.precision {
fractional.end = precision;
} else {
while fractional.end > 0
&& fractional.buf[fractional.end as usize - 1] == b'0'
{
fractional.end -= 1;
}
}
fractional
}
pub(crate) fn as_bytes(&self) -> &[u8] {
&self.buf[..usize::from(self.end)]
}
pub(crate) fn as_str(&self) -> &str {
unsafe { core::str::from_utf8_unchecked(self.as_bytes()) }
}
}
pub(crate) fn parse_temporal_fraction<'i>(
mut input: &'i [u8],
) -> Result<Parsed<'i, Option<t::SubsecNanosecond>>, Error> {
if input.is_empty() || (input[0] != b'.' && input[0] != b',') {
return Ok(Parsed { value: None, input });
}
input = &input[1..];
let mkdigits = parse::slicer(input);
while mkdigits(input).len() <= 8
&& input.first().map_or(false, u8::is_ascii_digit)
{
input = &input[1..];
}
let digits = mkdigits(input);
if digits.is_empty() {
return Err(err!(
"found decimal after seconds component, \
but did not find any decimal digits after decimal",
));
}
let nanoseconds = parse::fraction(digits, 9).map_err(|err| {
err!(
"failed to parse {digits:?} as fractional component \
(up to 9 digits, nanosecond precision): {err}",
digits = escape::Bytes(digits),
)
})?;
let nanoseconds = t::SubsecNanosecond::try_new("nanoseconds", nanoseconds)
.map_err(|err| err!("fractional nanoseconds are not valid: {err}"))?;
Ok(Parsed { value: Some(nanoseconds), input })
}
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use super::*;
#[test]
fn decimal() {
let x = DecimalFormatter::new().format(i64::MIN);
assert_eq!(x.as_str(), "-9223372036854775808");
let x = DecimalFormatter::new().format(i64::MIN + 1);
assert_eq!(x.as_str(), "-9223372036854775807");
let x = DecimalFormatter::new().format(i64::MAX);
assert_eq!(x.as_str(), "9223372036854775807");
let x = DecimalFormatter::new().force_sign(true).format(i64::MAX);
assert_eq!(x.as_str(), "+9223372036854775807");
let x = DecimalFormatter::new().format(0);
assert_eq!(x.as_str(), "0");
let x = DecimalFormatter::new().force_sign(true).format(0);
assert_eq!(x.as_str(), "+0");
let x = DecimalFormatter::new().force_sign(false).format(0);
assert_eq!(x.as_str(), "-0");
let x = DecimalFormatter::new().padding(4).format(0);
assert_eq!(x.as_str(), "0000");
let x = DecimalFormatter::new().padding(4).format(789);
assert_eq!(x.as_str(), "0789");
let x = DecimalFormatter::new().padding(4).format(-789);
assert_eq!(x.as_str(), "-0789");
let x =
DecimalFormatter::new().force_sign(true).padding(4).format(789);
assert_eq!(x.as_str(), "+0789");
}
#[test]
fn fractional_auto() {
let f = |n| FractionalFormatter::new().format(n).as_str().to_string();
assert_eq!(f(0), "");
assert_eq!(f(123_000_000), "123");
assert_eq!(f(123_456_000), "123456");
assert_eq!(f(123_456_789), "123456789");
assert_eq!(f(456_789), "000456789");
assert_eq!(f(789), "000000789");
}
#[test]
fn fractional_precision() {
let f = |precision, n| {
FractionalFormatter::new()
.precision(Some(precision))
.format(n)
.as_str()
.to_string()
};
assert_eq!(f(0, 0), "");
assert_eq!(f(1, 0), "0");
assert_eq!(f(9, 0), "000000000");
assert_eq!(f(3, 123_000_000), "123");
assert_eq!(f(6, 123_000_000), "123000");
assert_eq!(f(9, 123_000_000), "123000000");
assert_eq!(f(3, 123_456_000), "123");
assert_eq!(f(6, 123_456_000), "123456");
assert_eq!(f(9, 123_456_000), "123456000");
assert_eq!(f(3, 123_456_789), "123");
assert_eq!(f(6, 123_456_789), "123456");
assert_eq!(f(9, 123_456_789), "123456789");
assert_eq!(f(2, 889_000_000), "88");
assert_eq!(f(2, 999_000_000), "99");
}
}