1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/* Copyright (C) 2024 DorotaC
 * SPDX-License-Identifier: LGPL-2.1-or-later OR MPL-2.0
 */
/*! The const_enum macro */
#![no_std]

// The macro will get expanded within the context of the consuming crate, which may not have paste imported. This re-export allows the expanded macro to find `paste` and use it. Direct use by the consuming crate is discouraged. This re-export should be treated as an implementation detail.
#[doc(hidden)]
pub use paste::paste;

/// Turns FFI constants into an enum, freely convertible to and from u32.
/// Enum variants are turned into CamelCase identifiers.
/// An extra variant Other(u32) exists to contain values not known to this num.
/// The parameters are: Enum name, path to constants, constant prefix.
///
/// ```
/// use const_enum::const_enum;
/// mod sys {
///     pub const NAME_B: u32 = 1;
/// }
/// 
/// const_enum! {
/// // you can also use "this" as the path if the constants are in the same scope. It doesn't work in doctests, though, so I added the "mod sys".
///     enum TestEnum, sys, NAME_ {
///         B,
///     }
/// }
/// ```
#[macro_export]
macro_rules! const_enum {
    {enum $name:ident, $path:path, $prefix:ident {
        $(
            $( #[$meta:meta] )*
            $c:ident
        ),* $(,)?
    }} => {
        $crate::paste! {
            #[derive(Debug, Clone, Copy, PartialEq, Eq)]
            #[repr(u32)]
            pub enum $name {
                $(
                    $( #[$meta] )*
                    [<$c:camel>],
                )*
                /// Any value. Methods on this type will always prefer to create one of the above variants. Values covered by them can only be created by instantiating this variant explicitly. 
                Other(u32),
            }
        }
        impl From<u32> for $name {
            fn from(v: u32) -> Self {
                match v {
                    $(
                        $crate::paste! { $path::[<$prefix$c>] }
                        => $crate::paste! { Self::[<$c:camel>] },
                    )*
                    other => Self::Other(other),
                }
            }
        }
        impl From<$name> for u32 {
            fn from(v: $name) -> Self {
                match v {
                    $(
                        $crate::paste! { $name::[<$c:camel>] } => 
                        $crate::paste! { $path::[<$prefix$c>] },
                    )*
                    $name::Other(v) => v,
                }
            }
        }
    };
}

#[cfg(test)]
mod test {
    const NAME_B: u32 = 1;
    
    const_enum! {
        enum TestEnum, self, NAME_ {
            B,
        }
    }

    #[test]
    fn test_from() {
        assert_eq!(TestEnum::from(1), TestEnum::B);
        assert_eq!(TestEnum::from(2), TestEnum::Other(2));
    }
    #[test]
    fn test_to() {
        assert_eq!(u32::from(TestEnum::B), 1);
        assert_eq!(u32::from(TestEnum::Other(2)), 2);
    }
    #[test]
    fn test_bad_other() {
        // It's undesired but allowed to construct an Other with a value that better matches another enum variant
        assert_eq!(u32::from(TestEnum::B), u32::from(TestEnum::Other(1)));
    }
}