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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, parse_quote};
#[proc_macro_attribute]
pub fn pam_enum(_: TokenStream, input: TokenStream) -> TokenStream {
let item = parse_macro_input!(input as syn::Item);
let mut def = match item {
syn::Item::Enum(ie) => ie,
_ => {
return TokenStream::from(
syn::Error::new_spanned(item, "[pam_enum] only works on enums").to_compile_error(),
);
}
};
let variants: Vec<_> = def.variants.iter().cloned().collect();
let idents: Vec<_> = def
.variants
.iter()
.map(|v| prefix_ident(&v.ident, "PAM_"))
.collect();
def.attrs.extend(derive_attrs());
def.variants = build_variants(&variants, &idents);
let impl_block = build_impl_block(&def.ident, &variants, &idents);
let output = quote! {
#def
#impl_block
};
output.into()
}
fn derive_attrs() -> Vec<syn::Attribute> {
vec![
parse_quote! { #[repr(C)] },
parse_quote! { #[derive(Debug)] },
parse_quote! { #[derive(Copy)] },
parse_quote! { #[derive(Clone)] },
parse_quote! { #[derive(PartialEq)] },
]
}
fn build_variants(
variants: &[syn::Variant],
idents: &[syn::Ident],
) -> syn::punctuated::Punctuated<syn::Variant, syn::token::Comma> {
variants
.iter()
.zip(idents)
.map(|(var, id)| {
let mut var = var.clone();
if var.discriminant.is_none() {
var.discriminant = Some((parse_quote!(=), parse_quote!(pam_sys::#id as isize)));
}
var
})
.collect()
}
fn build_impl_block(
enum_name: &syn::Ident,
variants: &[syn::Variant],
idents: &[syn::Ident],
) -> syn::ItemImpl {
let default = &variants[0].ident;
let arms: Vec<syn::Arm> = variants
.iter()
.zip(idents)
.map(|(var, id)| {
let v_id = &var.ident;
if let Some((_, ref expr)) = var.discriminant {
parse_quote!(#expr => #enum_name::#v_id,)
} else {
parse_quote!(x if x as u32 == pam_sys::#id => #enum_name::#v_id,)
}
})
.collect();
parse_quote! {
impl std::convert::From<i32> for #enum_name {
fn from(value: i32) -> Self {
match value {
#(#arms)*
_ => #enum_name::#default
}
}
}
}
}
fn prefix_ident(ident: &syn::Ident, prefix: &str) -> syn::Ident {
syn::Ident::new(
&format!("{}{}", prefix, ident.to_string().to_uppercase()),
ident.span(),
)
}