1#![doc(hidden)]
2
3use core::mem::transmute;
4use core::mem::MaybeUninit;
5use core::str::from_utf8_unchecked;
6use paste::paste;
7
8pub struct Buffer<const N: usize> {
9 data: [MaybeUninit<u8>; N],
10 position: usize,
11}
12
13impl<const N: usize> Buffer<N> {
14 pub const fn new() -> Self {
15 Self {
16 data: [MaybeUninit::uninit(); N],
17 position: 0,
18 }
19 }
20
21 pub fn write_byte(&mut self, ch: u8) {
22 self.data[self.position].write(ch);
23 self.position += 1;
24 }
25
26 pub fn write_str_infallible(&mut self, s: &str) {
27 let bytes = s.as_bytes();
28 let n = bytes.len().min(N - self.position);
29 let src = &mut self.data[self.position..(self.position + n)];
30 let uninit_src: &mut [u8] = unsafe { transmute(src) };
31 uninit_src.copy_from_slice(&bytes[..n]);
32 self.position += n;
33 }
34
35 pub fn as_slice(&self) -> &[u8] {
36 unsafe { transmute(&self.data[..self.position]) }
37 }
38
39 pub unsafe fn as_str(&self) -> &str {
40 from_utf8_unchecked(self.as_slice())
41 }
42}
43
44impl<const N: usize> Default for Buffer<N> {
45 fn default() -> Self {
46 Self::new()
47 }
48}
49
50macro_rules! parameterize_width {
51 (linear_search, $uint: ident, ($($ilog_left: expr,)+), $ilog_midpoint: expr, ($($ilog_right: expr,)+)) => {
53 paste! {
54 #[inline]
55 const fn [<width_ $uint>](value: $uint) -> u8 {
56 $(
57 {
58 const POW: $uint = (10 as $uint).pow($ilog_left);
59 if value <= POW {
60 return $ilog_left;
61 }
62 }
63 )+
64 $(
65 {
66 const POW: $uint = (10 as $uint).pow($ilog_right);
67 if value <= POW {
68 return $ilog_right;
69 }
70 }
71 )+
72 $uint::MAX.ilog10() as u8 + 1
73 }
74 }
75 };
76 (bisection_1, $uint: ident, ($($ilog_left: expr,)+), $ilog_midpoint: expr, ($($ilog_right: expr,)+)) => {
78 paste! {
79 #[inline]
80 const fn [<width_ $uint>](value: $uint) -> u8 {
81 const MIDPOINT: $uint = (10 as $uint).pow($ilog_midpoint).next_power_of_two();
82 if value <= MIDPOINT {
83 $(
84 {
85 const POW: $uint = (10 as $uint).pow($ilog_left);
86 if value < POW {
87 return $ilog_left;
88 }
89 }
90 )+
91 $ilog_midpoint as u8 + 1
92 } else {
93 $(
94 {
95 const POW: $uint = (10 as $uint).pow($ilog_right);
96 if value < POW {
97 return $ilog_right;
98 }
99 }
100 )+
101 $uint::MAX.ilog10() as u8 + 1
102 }
103 }
104 }
105 };
106 (ilog10, $uint: ident, ($($ilog_left: expr,)+), $ilog_midpoint: expr, ($($ilog_right: expr,)+)) => {
108 paste! {
109 #[inline]
110 const fn [<width_ $uint>](value: $uint) -> u8 {
111 value.ilog10() as u8 + 1
112 }
113 }
114 };
115}
116
117macro_rules! parameterize {
118 ($(
119 $uint: ident,
120 $algo: ident,
121 (($($ilog_left: expr,)+), $ilog_midpoint: expr, ($($ilog_right: expr,)+)),
122 )+) => {
123 paste! {
124 $(
125 impl<const N: usize> Buffer<N> {
126 pub fn [<write_ $uint>](&mut self, mut n: $uint) {
127 if n == 0 {
128 self.write_byte(b'0');
129 return;
130 }
131 let width = [<width_ $uint>](n);
132 let old_position = self.position;
133 self.position += width as usize;
134 for i in (old_position..self.position).rev() {
135 let digit = n % 10;
136 n /= 10;
137 self.data[i].write(b'0' + digit as u8);
138 }
139 }
140 }
141
142 parameterize_width! {
143 $algo,
144 $uint,
145 ($($ilog_left,)+),
146 $ilog_midpoint,
147 ($($ilog_right,)+)
148 }
149 )+
150
151 #[cfg(test)]
152 mod buffer_tests {
153 use super::*;
154 use alloc::format;
155 use arbtest::arbtest;
156
157 extern crate alloc;
158
159 $(
160 #[test]
161 fn [<test_write_ $uint>]() {
162 arbtest(|u| {
163 let number: $uint = u.arbitrary()?;
164 let expected = format!("{}", number);
165 let mut buf = Buffer::<64>::new();
166 buf.[<write_ $uint>](number);
167 let actual = unsafe { buf.as_str() };
168 assert_eq!(expected, actual, "number = {number}");
169 Ok(())
170 });
171 }
172
173 #[test]
174 fn [<test_write_ $uint _power_of_1024>]() {
175 arbtest(|u| {
176 let number: $uint = u.int_in_range(0 as $uint..=$uint::MAX/1024)? * 1024;
177 let expected = format!("{}", number);
178 let mut buf = Buffer::<64>::new();
179 buf.[<write_ $uint>](number);
180 let actual = unsafe { buf.as_str() };
181 assert_eq!(expected, actual, "number = {number}");
182 Ok(())
183 });
184 }
185 )+
186 }
187 }
188 };
189}
190
191parameterize! {
192 u128, bisection_1,
193 ((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,),
194 20,
195 (20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,)),
196 u64, bisection_1,
197 ((1, 2, 3, 4, 5, 6, 7, 8, 9,), 10, (10, 11, 12, 13, 14, 15, 16, 17, 18, 19,)),
198 u32, ilog10, ((1, 2, 3, 4,), 5, (5, 6, 7, 8, 9,)),
199 u16, bisection_1, ((1, 2,), 2, (3, 4,)),
200}
201
202impl<const N: usize> core::fmt::Write for Buffer<N> {
203 fn write_str(&mut self, s: &str) -> core::fmt::Result {
204 self.write_str_infallible(s);
205 Ok(())
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212
213 #[test]
214 fn test_max_power_of_10() {
215 macro_rules! check {
216 ($uint: ident, $ilog: expr, $ilog_midpoint: expr) => {{
217 paste! {
218 const ILOG: u32 = $uint::MAX.ilog(10);
219 assert_eq!(ILOG, $ilog);
220 const MAX_POWER_OF_10: $uint = (10 as $uint).pow(ILOG);
221 assert_eq!(None, MAX_POWER_OF_10.checked_mul(10));
222 const MIDPOINT: $uint = (10 as $uint).pow($ilog_midpoint);
223 assert_eq!($ilog_midpoint, MIDPOINT.next_power_of_two().ilog10());
224 }
225 }};
226 }
227 check!(u16, 4, 3);
228 check!(u32, 9, 5);
229 check!(u64, 19, 10);
230 check!(u128, 38, 20);
231 }
232}