1#![expect(clippy::unreadable_literal, reason = "Macros made me do it.")]
6
7use crate::{
8 int,
9 traits::BytesToUnsigned,
10};
11use std::num::{
12 NonZeroI8,
13 NonZeroI16,
14 NonZeroI32,
15 NonZeroI64,
16 NonZeroI128,
17 NonZeroIsize,
18};
19
20
21
22pub trait BytesToSigned: Sized {
39 fn btoi(src: &[u8]) -> Option<Self>;
41}
42
43
44
45macro_rules! docs {
47 ($ty:ident) => (concat!(
48"# (ASCII) Bytes to Signed.
49
50Parse a `", stringify!($ty), "` from an ASCII byte slice.
51
52## Examples
53
54```
55use dactyl::traits::BytesToSigned;
56
57assert_eq!(
58 ", stringify!($ty), "::btoi(b\"", int!(@min $ty), "\"),
59 Some(", stringify!($ty), "::MIN),
60);
61assert_eq!(
62 ", stringify!($ty), "::btoi(b\"", int!(@max $ty), "\"),
63 Some(", stringify!($ty), "::MAX),
64);
65
66// Leading zeroes are fine.
67assert_eq!(
68 ", stringify!($ty), "::btoi(b\"00000123\"),
69 Some(123),
70);
71
72// These are all bad.
73assert!(", stringify!($ty), "::btoi(&[]).is_none()); // Empty.
74assert!(", stringify!($ty), "::btoi(b\" 2223231 \").is_none()); // Whitespace.
75assert!(", stringify!($ty), "::btoi(b\"nope\").is_none()); // Not a number.
76assert!(", stringify!($ty), "::btoi(
77 b\"4402823669209384634633746074317682114550\").is_none()
78); // Too big.
79```
80"
81 ));
82}
83
84
85
86macro_rules! signed {
88 ($ty:ident $min:literal) => (
89 impl BytesToSigned for $ty {
90 #[expect(clippy::cast_possible_wrap, reason = "False positive.")]
91 #[inline]
92 #[doc = docs!($ty)]
93 fn btoi(src: &[u8]) -> Option<Self> {
94 let (neg, src) = strip_sign(src)?;
95 let val = <int!(@flip $ty)>::btou(src)?;
96
97 if neg {
99 if val == $min { Some(<$ty>::MIN) }
100 else if val < $min { Some(0 - val as $ty) }
101 else { None }
102 }
103 else if val <= int!(@max $ty) { Some(val as $ty) }
105 else { None }
106 }
107 }
108 );
109}
110
111signed!(i8 128);
112signed!(i16 32768);
113signed!(i32 2147483648);
114signed!(i64 9223372036854775808);
115signed!(i128 170141183460469231731687303715884105728);
116
117#[cfg(target_pointer_width = "16")]
118signed!(isize 32768);
119
120#[cfg(target_pointer_width = "32")]
121signed!(isize 2147483648);
122
123#[cfg(target_pointer_width = "64")]
124signed!(isize 9223372036854775808);
125
126macro_rules! nz {
128 ($($ty:ident)+) => ($(
129 impl BytesToSigned for $ty {
130 #[inline]
131 fn btoi(src: &[u8]) -> Option<Self> {
133 <int!(@alias $ty)>::btoi(src).and_then(Self::new)
134 }
135 }
136 )+);
137}
138
139nz! { NonZeroI8 NonZeroI16 NonZeroI32 NonZeroI64 NonZeroI128 NonZeroIsize }
140
141
142
143const fn strip_sign(src: &[u8]) -> Option<(bool, &[u8])> {
151 match src {
152 [] => None,
153 [b'-', rest @ ..] => Some((true, rest)),
154 [b'+', rest @ ..] => Some((false, rest)),
155 _ => Some((false, src)),
156 }
157}
158
159
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 #[cfg(not(miri))]
166 const SAMPLE_SIZE: usize = 1_000_000;
167
168 #[cfg(miri)]
169 const SAMPLE_SIZE: usize = 500; macro_rules! test {
172 (@eq $ty:ident, $raw:expr, $expected:expr $(,)?) => (
173 assert_eq!(
174 <$ty>::btoi($raw),
175 $expected,
176 concat!(stringify!($ty), "::btoi({:?})"),
177 $raw,
178 );
179 );
180
181 ($($fn:ident $ty:ident),+ $(,)?) => ($(
182 #[test]
183 fn $fn() {
184 use std::num::NonZero;
185
186 test!(@eq $ty, b"", None);
188 test!(@eq $ty, b" 1", None);
189 test!(@eq $ty, b"1.0", None);
190 test!(@eq $ty, b"apples", None);
191
192 test!(@eq $ty, b"+123", Some(123));
194
195 test!(@eq $ty, b"0", Some(0));
197 test!(@eq $ty, b"00", Some(0));
198 test!(@eq $ty, b"0000", Some(0));
199 test!(@eq $ty, b"00000000", Some(0));
200 test!(@eq $ty, b"0000000000000000", Some(0));
201 test!(@eq $ty, b"000000000000000000000000000000000000000000000000", Some(0));
202
203 test!(@eq $ty, concat!(int!(@max $ty)).as_bytes(), Some(<$ty>::MAX));
205 test!(@eq $ty, concat!("0", int!(@max $ty)).as_bytes(), Some(<$ty>::MAX));
206
207 test!(@eq $ty, concat!(int!(@max $ty), "0").as_bytes(), None);
209
210 if size_of::<$ty>() == 1 {
212 for i in <$ty>::MIN..<$ty>::MAX {
213 let s = i.to_string();
214 test!(@eq $ty, s.as_bytes(), Some(i));
215 assert_eq!(
216 <NonZero<$ty>>::btoi(s.as_bytes()),
217 NonZero::<$ty>::new(i),
218 );
219 }
220 return;
221 }
222
223 let mut rng = fastrand::Rng::new();
225 for i in std::iter::repeat_with(|| rng.$ty(..)).take(SAMPLE_SIZE) {
226 let s = i.to_string();
227 test!(@eq $ty, s.as_bytes(), Some(i));
228 assert_eq!(
229 <NonZero<$ty>>::btoi(s.as_bytes()),
230 NonZero::<$ty>::new(i),
231 );
232 }
233 }
234 )+);
235 }
236
237 test!(
238 t_i8 i8,
239 t_i16 i16,
240 t_i32 i32,
241 t_i64 i64,
242 t_i128 i128,
243 t_isize isize,
244 );
245}