use alloc::str::from_utf8_unchecked;
use alloc::string::String;
use crate::error::ErrorKind;
#[derive(Default)]
pub struct Padding {
pub byte: u8,
pub count: usize,
}
impl From<usize> for Padding {
#[inline]
fn from(value: usize) -> Self {
Self {
byte: b'0',
count: value,
}
}
}
impl From<Option<usize>> for Padding {
#[inline]
fn from(value: Option<usize>) -> Self {
value.map_or_else(Self::default, Into::into)
}
}
pub fn write_two_digit_i32(out: &mut String, value: u8) {
debug_assert!(value <= 99);
out.push((b'0' + value / 10) as char);
out.push((b'0' + value % 10) as char);
}
#[allow(unsafe_code)]
pub fn write_i32(out: &mut String, padding: impl Into<Padding>, mut value: i32) {
let padding = padding.into();
let mut buffer = [padding.byte; 10];
let mut index = 9;
let mut count = 0;
while value != 0 {
buffer[index] = b'0' + ((value % 10) as u8);
value /= 10;
index -= 1;
count += 1;
}
let s = &buffer[10 - count.max(padding.count)..];
let s = unsafe { from_utf8_unchecked(s) };
out.push_str(s);
}
pub fn read_ensure_u8(input: &mut &[u8], expected: u8) -> crate::Result<()> {
read_ensure_one_of_u8(input, &[expected])
}
pub fn read_ensure_one_of_u8(input: &mut &[u8], expected: &[u8]) -> crate::Result<()> {
match input.first().copied() {
Some(byte) if expected.contains(&byte) => {
*input = &input[1..];
Ok(())
}
_ => Err(ErrorKind::ParseInvalid.into()),
}
}
#[derive(Debug, Copy, Clone)]
pub enum ReadDigits {
Exactly(usize),
AtLeast(usize),
}
impl From<usize> for ReadDigits {
fn from(value: usize) -> Self {
Self::Exactly(value)
}
}
impl From<Option<Self>> for ReadDigits {
fn from(value: Option<Self>) -> Self {
value.unwrap_or(Self::AtLeast(1))
}
}
pub fn read_i32(
input: &mut &[u8],
digits: impl Into<ReadDigits>,
) -> crate::Result<Option<(i32, usize)>> {
let digits = digits.into();
let mut len = 0;
let mut value: i32 = 0;
while let Some(byte) = input.get(len) {
if !byte.is_ascii_digit() {
if len > 0 {
break;
}
return Err(ErrorKind::ParseInvalid.into());
}
let digit = (byte - b'0') as i32;
if len > 0 {
value = if let Some(value) = value.checked_mul(10) {
value
} else {
return Ok(None);
};
}
value += digit;
len += 1;
}
match digits {
ReadDigits::Exactly(digits) => {
if len != digits {
return Err(ErrorKind::ParseInvalid.into());
}
}
ReadDigits::AtLeast(digits) => {
if len < digits {
if len >= input.len() {
return Err(ErrorKind::ParseNotEnough.into());
}
return Err(ErrorKind::ParseInvalid.into());
}
}
}
*input = &input[len..];
Ok(Some((value, len)))
}
pub fn read_i32_in_range(
input: &mut &[u8],
digits: impl Into<ReadDigits>,
min: i32,
max: i32,
) -> crate::Result<(i32, usize)> {
match read_i32(input, digits)? {
Some((number, len)) if (min..=max).contains(&number) => Ok((number, len)),
_ => Err(ErrorKind::OutOfRange.into()),
}
}
pub fn read_sign_is_negative(input: &mut &[u8], required: bool) -> crate::Result<bool> {
let negative = input.first().copied().and_then(|maybe_sign| {
let sign = match maybe_sign {
b'+' => false,
b'-' => true,
_ => {
return None;
}
};
*input = &input[1..];
Some(sign)
});
match negative {
Some(negative) => Ok(negative),
None if required => Err((if input.is_empty() {
ErrorKind::ParseNotEnough
} else {
ErrorKind::ParseInvalid
})
.into()),
None => Ok(false),
}
}
#[cfg(test)]
mod tests {
use alloc::string::String;
use test_case::test_case;
use crate::fmt::rw::{read_i32, write_i32, write_two_digit_i32};
#[test_case("00", 0)]
#[test_case("05", 5)]
#[test_case("10", 10)]
#[test_case("49", 49)]
fn expect_write_two_digit_i32(text: &str, value: i32) -> crate::Result<()> {
let mut output = String::with_capacity(2);
write_two_digit_i32(&mut output, value as u8);
assert_eq!(output, text);
Ok(())
}
#[test_case("5", 5)]
#[test_case("10", 10)]
#[test_case("3278", 3278)]
fn expect_i32(text: &str, expected: i32) -> crate::Result<()> {
let mut output = String::with_capacity(4);
let mut bytes = text.as_bytes();
let (value, _) = read_i32(&mut bytes, None)?.unwrap();
write_i32(&mut output, None, value);
assert_eq!(value, expected);
assert_eq!(output, text);
Ok(())
}
#[test_case("005", 3, 5)]
#[test_case("010", 3, 10)]
#[test_case("3278", 3, 3278)]
fn expect_write_i32_padding(text: &str, padding: usize, expected: i32) -> crate::Result<()> {
let mut output = String::with_capacity(4);
let mut bytes = text.as_bytes();
let (value, _) = read_i32(&mut bytes, None)?.unwrap();
write_i32(&mut output, Some(padding), expected);
assert_eq!(output, text);
assert_eq!(value, expected);
Ok(())
}
}