1pub 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_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 #[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 #[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 #[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}