small_num/
lib.rs

1//! Small numbers can be used to describe some constrained data.
2//! For example, a number of alignment bytes:
3//! 
4//! ```rust
5//! use small_num::small_num;
6//! 
7//! small_num! {
8//!     #[derive(Clone, Debug, PartialEq, Copy, Eq, PartialOrd, Ord, Hash)]
9//!     pub enum AlignBytes: [1, 2, 4];
10//! }
11//! 
12//! let bytes = AlignBytes::new(2);
13//! assert_eq!(bytes, Some(AlignBytes::_2));
14//! assert_eq!(AlignBytes::new(3), None);
15//! assert_eq!(std::mem::size_of::<Option<AlignBytes>>(), 1);
16//! ```
17//! 
18//! Or an integer valid for a spicific range:
19//! 
20//! ```rust
21//! use small_num::small_num;
22//! 
23//! small_num! {
24//!     #[derive(Clone, Debug, PartialEq, Copy, Eq, PartialOrd, Ord, Hash)]
25//!     pub enum U7: ..128;
26//! }
27//! 
28//! assert_eq!(U7::new(0), Some(U7::_0));
29//! assert_eq!(U7::new(50), Some(U7::_50));
30//! assert_eq!(U7::new(127), Some(U7::_127));
31//! assert_eq!(U7::new(128), None);
32//! assert_eq!(std::mem::size_of::<Option<U7>>(), 1);
33//! ```
34//! 
35//! It can be casted to an integer with `as` operator:
36//! 
37//! ```rust
38//! use small_num::small_num;
39//! 
40//! small_num! {
41//!     #[derive(Clone, Debug, PartialEq, Copy, Eq, PartialOrd, Ord, Hash)]
42//!     pub enum Num50to100: 50..=100;
43//! }
44//! 
45//! assert_eq!(Num50to100::new(69).unwrap() as u32, 69);
46//! ```
47//! 
48//! Where clause can be used to derive supported traits:
49//! 
50//! ```rust
51//! use small_num::small_num;
52//! 
53//! small_num! {
54//!     #[derive(Clone)]
55//!     pub enum MySmallNumber: [0, 42, 121]
56//!     where
57//!         Self: Debug + Serialize;
58//! }
59//! ```
60//! 
61//! Supported traits in the where clause:
62//! 
63//! - `Debug` - prints your small number as a regular number
64//! - `Serialize` (with `serde` feature)
65//! - `Deserialize` (with `serde` feature)
66//! 
67//! ## Crate features
68//! 
69//! - `serde` enables `Serialize` and `Deserialize` derivation.
70
71
72
73pub mod util {
74    pub use seq_macro::seq;
75    pub use paste::paste;
76
77    #[cfg(feature = "serde")]
78    pub use serde;
79}
80
81
82
83/// Macro used to create small numbers. It comes in two forms: as a range
84/// and a list of valid values.
85/// 
86/// ## List form
87/// 
88/// It creates enum with given list of values.
89/// 
90/// ```rust,ignore
91/// small_num! {
92///     pub enum ListNum: [<list of values>];
93/// }
94/// ```
95/// 
96/// ## Range form
97/// 
98/// It creates enum with all numbers from given range.
99/// 
100/// ```rust,ignore
101/// small_num! {
102///     pub enum RangeNum: <START>..<END>;
103/// }
104/// ```
105/// 
106/// ## `small-num` custom traits derivation
107/// 
108/// ```rust,ignore
109/// small_num! {
110///     pub enum Num: [10, 9, 8, 7];
111///     where
112///         Self: <TRAIT_1> + <TRAIT_2>;
113/// }
114/// ```
115#[macro_export]
116macro_rules! small_num {
117    (
118        $( #[$meta:meta] )*
119        $vis:vis enum $Name:ident : [ $($value:literal),* $(,)? ]
120        $(
121            where
122                Self: $FirstTrait:ident $( + $TailTraits:ident )*
123        )?
124        ;
125    ) => {
126        $crate::util::paste! {
127            $( #[$meta] )*
128            $vis enum $Name {
129                $(
130                    [<_ $value>] = $value,
131                )*
132            }
133        }
134
135        $crate::small_num! {
136            @derive( $( $FirstTrait, $( $TailTraits, )* )? )
137            $Name
138        }
139
140        impl $Name {
141            #[allow(dead_code)]
142            $vis const fn new(value: u32) -> ::core::option::Option<Self> {
143                $crate::util::paste! {
144                    ::core::option::Option::Some(match value {
145                        $(
146                            $value => Self::[<_ $value>],
147                        )*
148                        _ => return ::core::option::Option::None,
149                    })
150                }
151            }
152
153            /// # Safety
154            /// 
155            /// `value` should be valid.
156            #[allow(dead_code)]
157            $vis const unsafe fn new_unchecked(value: u32) -> Self {
158                $crate::util::paste! {
159                    match value {
160                        $(
161                            $value => Self::[<_ $value>],
162                        )*
163                        _ => unsafe { ::core::hint::unreachable_unchecked() },
164                    }
165                }
166            }
167        }
168    };
169
170    (
171        $( #[$meta:meta] )*
172        $vis:vis enum $Name:ident : $start:literal..$end:literal
173        $(
174            where
175                Self: $FirstTrait:ident $( + $TailTraits:ident )*
176        )?
177        ;
178    ) => {
179        $crate::util::seq!(N in $start..$end {
180            $( #[$meta] )*
181            $vis enum $Name {
182                #(
183                    _~N = N,
184                )*
185            }
186        });
187
188        $crate::small_num! {
189            @derive( $( $FirstTrait, $( $TailTraits, )* )? )
190            $Name
191        }
192
193        impl $Name {
194            #[allow(dead_code)]
195            $vis const fn new(value: u32) -> ::core::option::Option<Self> {
196                $crate::util::seq!(N in $start..$end {
197                    ::core::option::Option::Some(match value {
198                        #( N => Self::_~N, )*
199                        _ => return ::core::option::Option::None,
200                    })
201                })
202            }
203
204            /// # Safety
205            /// 
206            /// `value` should be valid.
207            #[allow(dead_code)]
208            $vis const unsafe fn new_unchecked(value: u32) -> Self {
209                $crate::util::seq!(N in $start..$end {
210                    match value {
211                        #( N => Self::_~N, )*
212                        _ => unsafe { ::core::hint::unreachable_unchecked() },
213                    }
214                })
215            }
216        }
217    };
218
219    (
220        $( #[$meta:meta] )*
221        $vis:vis enum $Name:ident : $start:literal..=$end:literal
222        $(
223            where
224                Self: $FirstTrait:ident $( + $TailTrait:ident )*
225        )?
226        ;
227    ) => {
228        $crate::util::seq!(N in $start..=$end {
229            $( #[$meta] )*
230            $vis enum $Name {
231                #(
232                    _~N = N,
233                )*
234            }
235        });
236
237        $crate::small_num! {
238            @derive( $( $FirstTrait, $( $TailTraits, )* )? )
239            $Name
240        }
241
242        impl $Name {
243            #[allow(dead_code)]
244            $vis const fn new(value: u32) -> ::core::option::Option<Self> {
245                $crate::util::seq!(N in $start..=$end {
246                    ::core::option::Option::Some(match value {
247                        #( N => Self::_~N, )*
248                        _ => return None,
249                    })
250                })
251            }
252
253            /// # Safety
254            /// 
255            /// `value` should be valid.
256            #[allow(dead_code)]
257            $vis const unsafe fn new_unchecked(value: u32) -> Self {
258                $crate::util::seq!(N in $start..=$end {
259                    match value {
260                        #( N => Self::_~N, )*
261                        _ => ::core::hint::unreachable_unchecked(),
262                    }
263                })
264            }
265        }
266    };
267
268    (
269        $( #[$meta:meta] )*
270        $vis:vis enum $Name:ident : ..$end:literal
271        $(
272            where
273                Self: $FirstTrait:ident $( + $TailTraits:ident )*
274        )?
275        ;
276    ) => {
277        $crate::small_num! {
278            $( #[$meta] )*
279            $vis enum $Name : 0..$end
280            $(
281                where
282                    Self: $FirstTrait $( + $TailTraits )*
283            )?
284            ;
285        }
286    };
287
288    (
289        $( #[$meta:meta] )*
290        $vis:vis enum $Name:ident : ..=$end:literal
291        $(
292            where
293                Self: $FirstTrait:ident $( + $TailTraits:ident )*
294        )?
295        ;
296    ) => {
297        $crate::small_num! {
298            $( #[$meta] )*
299            $vis enum $Name : 0..=$end
300            $(
301                where
302                    Self: $FirstTrait $( + $TailTraits:ident )*
303            )?
304            ;
305        }
306    };
307
308    (
309        $( #[$meta:meta] )*
310        $vis:vis enum $Name:ident : $( $start:literal )? ..
311        $(
312            where
313                Self: $FirstTrait:ident $( + $TailTraits:ident )*
314        )?
315        ;
316    ) => {
317        ::core::compile_error!("RageTo in range_num is unsupported");
318    };
319
320    (
321        @derive(Serialize)
322        $Name:ident
323    ) => {
324        #[cfg(not(feature = "serde"))]
325        compile_error!(concat!(
326            "Enable `serde` feature to derive `Serialize` for `",
327            stringify!($Name),
328            '`',
329        ));
330
331        #[cfg(feature = "serde")]
332        impl $crate::util::serde::Serialize for $Name {
333            fn serialize<S>(&self, serializer: S) -> ::core::result::Result<S::Ok, S::Error>
334            where
335                S: $crate::util::serde::Serializer,
336            {
337                serializer.serialize_u32(self.clone() as u32)
338            }
339        }
340    };
341
342    (
343        @derive(Deserialize)
344        $Name:ident
345    ) => {
346        #[cfg(not(feature = "serde"))]
347        compile_error!(concat!(
348            "Enable `serde` feature to derive `Deserialize` for `",
349            stringify!($Name),
350            '`',
351        ));
352
353        #[cfg(feature = "serde")]
354        $crate::util::paste! {
355            impl<'de> $crate::util::serde::Deserialize<'de> for $Name {
356                fn deserialize<D>(deserializer: D) -> ::core::result::Result<Self, D::Error>
357                where
358                    D: $crate::util::serde::Deserializer<'de>,
359                {
360                    #[doc(hidden)]
361                    struct [<__ $Name Visitor>];
362
363                    const __EXPECTING_STR: &str = concat!("expecting valid small-num '", stringify!($Name), '\'');
364
365                    impl<'de> $crate::util::serde::de::Visitor<'de> for [<__ $Name Visitor>] {
366                        type Value = $Name;
367
368                        fn expecting(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
369                            f.write_str(__EXPECTING_STR)
370                        }
371
372                        fn visit_u32<E>(self, v: u32) -> ::core::result::Result<Self::Value, E>
373                        where
374                            E: $crate::util::serde::de::Error,
375                        {
376                            $Name::new(v).ok_or(E::invalid_value(
377                                $crate::util::serde::de::Unexpected::Unsigned(v.into()),
378                                &__EXPECTING_STR,
379                            ))
380                        }
381
382                        fn visit_u64<E>(self, v: u64) -> ::core::result::Result<Self::Value, E>
383                        where
384                            E: $crate::util::serde::de::Error,
385                        {
386                            let v = u32::try_from(v).map_err(|_| E::invalid_value(
387                                $crate::util::serde::de::Unexpected::Unsigned(v),
388                                &__EXPECTING_STR,
389                            ))?;
390
391                            self.visit_u32(v)
392                        }
393                    }
394
395                    deserializer.deserialize_u32([<__ $Name Visitor>])
396                }
397            }
398        }
399    };
400
401    (
402        @derive(Debug)
403        $Name:ident
404    ) => {
405        impl ::core::fmt::Debug for $Name {
406            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
407                write!(f, "{}", self.clone() as u32)
408            }
409        }
410    };
411
412    (
413        @derive( $Trait:ident )
414        $Name:ident
415    ) => {
416        compile_error!(concat!(
417            "Trait '",
418            stringify!($Trait),
419            "' can not be derived for '",
420            stringify!($Name),
421            "' by crate small-num.",
422        ));
423    };
424
425    (
426        @derive( $( $Trait:ident ),* $(,)? )
427        $Name:ident
428    ) => {
429        $(
430            $crate::small_num! {
431                @derive( $Trait )
432                $Name
433            }
434        )*
435    };
436}
437
438
439
440#[cfg(test)]
441mod tests {
442    use super::*;
443
444    #[test]
445    fn test_list() {
446        small_num! {
447            #[derive(Clone, PartialEq, Copy)]
448            pub enum ListNum: [1, 2, 4, 8]
449            where
450                Self: Debug;
451        }
452
453        assert_eq!(ListNum::new(1), Some(ListNum::_1));
454        assert_eq!(ListNum::new(2), Some(ListNum::_2));
455        assert_eq!(ListNum::new(4), Some(ListNum::_4));
456        assert_eq!(ListNum::new(8), Some(ListNum::_8));
457        assert_eq!(ListNum::new(9), None);
458    }
459
460    #[test]
461    fn test_range() {
462        small_num! {
463            #[derive(Clone, PartialEq, Copy)]
464            pub enum RangeNum: 10..15
465            where
466                Self: Debug;
467        }
468
469        assert_eq!(RangeNum::new(10), Some(RangeNum::_10));
470        assert_eq!(RangeNum::new(11), Some(RangeNum::_11));
471        assert_eq!(RangeNum::new(16), None);
472    }
473
474    #[test]
475    fn debug() {
476        small_num! {
477            #[derive(Clone)]
478            pub enum SmallNum: [1, 2, 3]
479            where
480                Self: Debug;
481        }
482
483        assert_eq!(format!("{:?}", SmallNum::_2), "2");
484    }
485}