-
Notifications
You must be signed in to change notification settings - Fork 13.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added functionality for
int_format_into
- Loading branch information
1 parent
2c6a12e
commit 040cedb
Showing
2 changed files
with
282 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,279 @@ | ||
use crate::mem::MaybeUninit; | ||
|
||
/// A minimal buffer implementation containing elements of type | ||
/// `MaybeUninit<u8>`. | ||
#[unstable(feature = "int_format_into", issue = "138215")] | ||
#[derive(Debug)] | ||
pub struct NumBuffer<const BUF_SIZE: usize> { | ||
/// A statically allocated array of elements of type | ||
/// `MaybeUninit::<u8>`. | ||
/// | ||
/// An alternative to `contents.len()` is `BUF_SIZE`. | ||
pub contents: [MaybeUninit<u8>; BUF_SIZE], | ||
} | ||
|
||
#[unstable(feature = "int_format_into", issue = "138215")] | ||
impl<const BUF_SIZE: usize> NumBuffer<BUF_SIZE> { | ||
/// Initializes `contents` as an uninitialized array of `MaybeUninit<u8>`. | ||
#[unstable(feature = "int_format_into", issue = "138215")] | ||
pub fn new() -> Self { | ||
NumBuffer { contents: [MaybeUninit::<u8>::uninit(); BUF_SIZE] } | ||
} | ||
} | ||
|
||
macro_rules! int_impl_format_into { | ||
($($T:ident)*) => { | ||
$( | ||
#[unstable(feature = "int_format_into", issue = "138215")] | ||
impl $T { | ||
/// Allows users to write an integer (in signed decimal format) into a variable `buf` of | ||
/// type [`NumBuffer`] that is passed by the caller by mutable reference. | ||
/// | ||
/// This function panics if `buf` does not have enough size to store | ||
/// the signed decimal version of the number. | ||
/// | ||
/// # Examples | ||
/// ``` | ||
/// #![feature(int_format_into)] | ||
/// | ||
/// use core::num::NumBuffer; | ||
#[doc = concat!("let n = -32", stringify!($T), ";")] | ||
/// let mut buf = NumBuffer::<3>::new(); | ||
/// | ||
/// assert_eq!(n.format_into(&mut buf), "-32"); | ||
/// ``` | ||
/// | ||
pub fn format_into<const BUF_SIZE: usize>(self, buf: &mut crate::num::NumBuffer<BUF_SIZE>) -> &str { | ||
// 2 digit decimal look up table | ||
const DEC_DIGITS_LUT: &[u8; 200] = b"\ | ||
0001020304050607080910111213141516171819\ | ||
2021222324252627282930313233343536373839\ | ||
4041424344454647484950515253545556575859\ | ||
6061626364656667686970717273747576777879\ | ||
8081828384858687888990919293949596979899"; | ||
|
||
const NEGATIVE_SIGN: &[u8; 1] = b"-"; | ||
|
||
// counting space for negative sign too, if `self` is negative | ||
let sign_offset = if self < 0 {1} else {0}; | ||
let decimal_string_size: usize = self.ilog(10) as usize + 1 + sign_offset; | ||
|
||
// `buf` must have minimum size to store the decimal string version. | ||
// BUF_SIZE is the size of the buffer. | ||
if BUF_SIZE < decimal_string_size { | ||
panic!("Not enough buffer size to format into!"); | ||
} | ||
|
||
// Count the number of bytes in `buf` that are not initialized. | ||
let mut offset = BUF_SIZE; | ||
// Consume the least-significant decimals from a working copy. | ||
let mut remain = self; | ||
|
||
// Format per four digits from the lookup table. | ||
// Four digits need a 16-bit $unsigned or wider. | ||
while size_of::<Self>() > 1 && remain > 999.try_into().expect("branch is not hit for types that cannot fit 999 (u8)") { | ||
// SAFETY: All of the decimals fit in buf, since it now is size-checked | ||
// and the while condition ensures at least 4 more decimals. | ||
unsafe { core::hint::assert_unchecked(offset >= 4) } | ||
// SAFETY: The offset counts down from its initial value BUF_SIZE | ||
// without underflow due to the previous precondition. | ||
unsafe { core::hint::assert_unchecked(offset <= BUF_SIZE) } | ||
offset -= 4; | ||
|
||
// pull two pairs | ||
let scale: Self = 1_00_00.try_into().expect("branch is not hit for types that cannot fit 1E4 (u8)"); | ||
let quad = remain % scale; | ||
remain /= scale; | ||
let pair1 = (quad / 100) as usize; | ||
let pair2 = (quad % 100) as usize; | ||
buf.contents[offset + 0].write(DEC_DIGITS_LUT[pair1 * 2 + 0]); | ||
buf.contents[offset + 1].write(DEC_DIGITS_LUT[pair1 * 2 + 1]); | ||
buf.contents[offset + 2].write(DEC_DIGITS_LUT[pair2 * 2 + 0]); | ||
buf.contents[offset + 3].write(DEC_DIGITS_LUT[pair2 * 2 + 1]); | ||
} | ||
|
||
// Format per two digits from the lookup table. | ||
if remain > 9 { | ||
// SAFETY: All of the decimals fit in buf, since it now is size-checked | ||
// and the while condition ensures at least 2 more decimals. | ||
unsafe { core::hint::assert_unchecked(offset >= 2) } | ||
// SAFETY: The offset counts down from its initial value BUF_SIZE | ||
// without underflow due to the previous precondition. | ||
unsafe { core::hint::assert_unchecked(offset <= BUF_SIZE) } | ||
offset -= 2; | ||
|
||
let pair = (remain % 100) as usize; | ||
remain /= 100; | ||
buf.contents[offset + 0].write(DEC_DIGITS_LUT[pair * 2 + 0]); | ||
buf.contents[offset + 1].write(DEC_DIGITS_LUT[pair * 2 + 1]); | ||
} | ||
|
||
// Format the last remaining digit, if any. | ||
if remain != 0 || self == 0 { | ||
// SAFETY: All of the decimals fit in buf, since it now is size-checked | ||
// and the if condition ensures (at least) 1 more decimals. | ||
unsafe { core::hint::assert_unchecked(offset >= 1) } | ||
// SAFETY: The offset counts down from its initial value BUF_SIZE | ||
// without underflow due to the previous precondition. | ||
unsafe { core::hint::assert_unchecked(offset <= BUF_SIZE) } | ||
offset -= 1; | ||
|
||
// Either the compiler sees that remain < 10, or it prevents | ||
// a boundary check up next. | ||
let last = (remain & 15) as usize; | ||
buf.contents[offset].write(DEC_DIGITS_LUT[last * 2 + 1]); | ||
// not used: remain = 0; | ||
} | ||
|
||
if self < 0 { | ||
// SAFETY: All of the decimals (with the sign) fit in buf, since it now is size-checked | ||
// and the if condition ensures (at least) that the sign can be added. | ||
unsafe { core::hint::assert_unchecked(offset >= 1) } | ||
|
||
// SAFETY: The offset counts down from its initial value BUF_SIZE | ||
// without underflow due to the previous precondition. | ||
unsafe { core::hint::assert_unchecked(offset <= BUF_SIZE) } | ||
|
||
// Setting sign for the negative number | ||
offset -= 1; | ||
buf.contents[offset].write(NEGATIVE_SIGN[0]); | ||
} | ||
|
||
// SAFETY: All buf content since offset is set. | ||
let written = unsafe { buf.contents.get_unchecked(offset..) }; | ||
|
||
// SAFETY: Writes use ASCII from the lookup table | ||
// (and `NEGATIVE_SIGN` in case of negative numbers) exclusively. | ||
let as_str = unsafe { | ||
str::from_utf8_unchecked(crate::slice::from_raw_parts( | ||
crate::mem::MaybeUninit::slice_as_ptr(written), | ||
written.len(), | ||
)) | ||
}; | ||
as_str | ||
} | ||
} | ||
)* | ||
}; | ||
} | ||
|
||
macro_rules! uint_impl_format_into { | ||
($($T:ident)*) => { | ||
$( | ||
#[unstable(feature = "int_format_into", issue = "138215")] | ||
impl $T { | ||
/// Allows users to write an integer (in signed decimal format) into a variable `buf` of | ||
/// type [`NumBuffer`] that is passed by the caller by mutable reference. | ||
/// | ||
/// This function panics if `buf` does not have enough size to store | ||
/// the signed decimal version of the number. | ||
/// | ||
/// # Examples | ||
/// ``` | ||
/// #![feature(int_format_into)] | ||
/// | ||
/// use core::num::NumBuffer; | ||
#[doc = concat!("let n = 32", stringify!($T), ";")] | ||
/// let mut buf = NumBuffer::<3>::new(); | ||
/// | ||
/// assert_eq!(n.format_into(&mut buf), "32"); | ||
/// ``` | ||
/// | ||
pub fn format_into<const BUF_SIZE: usize>(self, buf: &mut crate::num::NumBuffer<BUF_SIZE>) -> &str { | ||
// 2 digit decimal look up table | ||
const DEC_DIGITS_LUT: &[u8; 200] = b"\ | ||
0001020304050607080910111213141516171819\ | ||
2021222324252627282930313233343536373839\ | ||
4041424344454647484950515253545556575859\ | ||
6061626364656667686970717273747576777879\ | ||
8081828384858687888990919293949596979899"; | ||
|
||
// counting space for negative sign too, if `self` is negative | ||
let decimal_string_size: usize = self.ilog(10) as usize + 1; | ||
|
||
// `buf` must have minimum size to store the decimal string version. | ||
// BUF_SIZE is the size of the buffer. | ||
if BUF_SIZE < decimal_string_size { | ||
panic!("Not enough buffer size to format into!"); | ||
} | ||
|
||
// Count the number of bytes in `buf` that are not initialized. | ||
let mut offset = BUF_SIZE; | ||
// Consume the least-significant decimals from a working copy. | ||
let mut remain = self; | ||
|
||
// Format per four digits from the lookup table. | ||
// Four digits need a 16-bit $unsigned or wider. | ||
while size_of::<Self>() > 1 && remain > 999.try_into().expect("branch is not hit for types that cannot fit 999 (u8)") { | ||
// SAFETY: All of the decimals fit in buf, since it now is size-checked | ||
// and the while condition ensures at least 4 more decimals. | ||
unsafe { core::hint::assert_unchecked(offset >= 4) } | ||
// SAFETY: The offset counts down from its initial value BUF_SIZE | ||
// without underflow due to the previous precondition. | ||
unsafe { core::hint::assert_unchecked(offset <= BUF_SIZE) } | ||
offset -= 4; | ||
|
||
// pull two pairs | ||
let scale: Self = 1_00_00.try_into().expect("branch is not hit for types that cannot fit 1E4 (u8)"); | ||
let quad = remain % scale; | ||
remain /= scale; | ||
let pair1 = (quad / 100) as usize; | ||
let pair2 = (quad % 100) as usize; | ||
buf.contents[offset + 0].write(DEC_DIGITS_LUT[pair1 * 2 + 0]); | ||
buf.contents[offset + 1].write(DEC_DIGITS_LUT[pair1 * 2 + 1]); | ||
buf.contents[offset + 2].write(DEC_DIGITS_LUT[pair2 * 2 + 0]); | ||
buf.contents[offset + 3].write(DEC_DIGITS_LUT[pair2 * 2 + 1]); | ||
} | ||
|
||
// Format per two digits from the lookup table. | ||
if remain > 9 { | ||
// SAFETY: All of the decimals fit in buf, since it now is size-checked | ||
// and the while condition ensures at least 2 more decimals. | ||
unsafe { core::hint::assert_unchecked(offset >= 2) } | ||
// SAFETY: The offset counts down from its initial value BUF_SIZE | ||
// without underflow due to the previous precondition. | ||
unsafe { core::hint::assert_unchecked(offset <= BUF_SIZE) } | ||
offset -= 2; | ||
|
||
let pair = (remain % 100) as usize; | ||
remain /= 100; | ||
buf.contents[offset + 0].write(DEC_DIGITS_LUT[pair * 2 + 0]); | ||
buf.contents[offset + 1].write(DEC_DIGITS_LUT[pair * 2 + 1]); | ||
} | ||
|
||
// Format the last remaining digit, if any. | ||
if remain != 0 || self == 0 { | ||
// SAFETY: All of the decimals fit in buf, since it now is size-checked | ||
// and the if condition ensures (at least) 1 more decimals. | ||
unsafe { core::hint::assert_unchecked(offset >= 1) } | ||
// SAFETY: The offset counts down from its initial value BUF_SIZE | ||
// without underflow due to the previous precondition. | ||
unsafe { core::hint::assert_unchecked(offset <= BUF_SIZE) } | ||
offset -= 1; | ||
|
||
// Either the compiler sees that remain < 10, or it prevents | ||
// a boundary check up next. | ||
let last = (remain & 15) as usize; | ||
buf.contents[offset].write(DEC_DIGITS_LUT[last * 2 + 1]); | ||
// not used: remain = 0; | ||
} | ||
|
||
// SAFETY: All buf content since offset is set. | ||
let written = unsafe { buf.contents.get_unchecked(offset..) }; | ||
|
||
// SAFETY: Writes use ASCII from the lookup table exclusively. | ||
let as_str = unsafe { | ||
str::from_utf8_unchecked(crate::slice::from_raw_parts( | ||
crate::mem::MaybeUninit::slice_as_ptr(written), | ||
written.len(), | ||
)) | ||
}; | ||
as_str | ||
} | ||
} | ||
)* | ||
}; | ||
} | ||
|
||
int_impl_format_into! { i8 i16 i32 i64 i128 isize } | ||
uint_impl_format_into! { u8 u16 u32 u64 u128 usize } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters