1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4
5use quote::quote;
6use syn::{parse_macro_input, parse_quote};
7
8#[proc_macro_attribute]
9pub fn pam_enum(_: TokenStream, input: TokenStream) -> TokenStream {
10 let item = parse_macro_input!(input as syn::Item);
11
12 let mut def = match item {
14 syn::Item::Enum(ie) => ie,
15 _ => {
16 return TokenStream::from(
18 syn::Error::new_spanned(item, "[pam_enum] only works on enums").to_compile_error(),
19 );
20 }
21 };
22
23 let variants: Vec<_> = def.variants.iter().cloned().collect();
25 let idents: Vec<_> = def
26 .variants
27 .iter()
28 .map(|v| prefix_ident(&v.ident, "PAM_"))
29 .collect();
30
31 def.attrs.extend(derive_attrs());
33
34 def.variants = build_variants(&variants, &idents);
36
37 let impl_block = build_impl_block(&def.ident, &variants, &idents);
39
40 let output = quote! {
42 #def
43 #impl_block
44 };
45
46 output.into()
47}
48
49fn derive_attrs() -> Vec<syn::Attribute> {
50 vec![
51 parse_quote! { #[repr(C)] },
52 parse_quote! { #[derive(Debug)] },
53 parse_quote! { #[derive(Copy)] },
54 parse_quote! { #[derive(Clone)] },
55 parse_quote! { #[derive(PartialEq)] },
56 ]
57}
58
59fn build_variants(
60 variants: &[syn::Variant],
61 idents: &[syn::Ident],
62) -> syn::punctuated::Punctuated<syn::Variant, syn::token::Comma> {
63 variants
64 .iter()
65 .zip(idents)
66 .map(|(var, id)| {
67 let mut var = var.clone();
68 if var.discriminant.is_none() {
70 var.discriminant = Some((parse_quote!(=), parse_quote!(pam_sys::#id as isize)));
71 }
72 var
73 })
74 .collect()
75}
76
77fn build_impl_block(
78 enum_name: &syn::Ident,
79 variants: &[syn::Variant],
80 idents: &[syn::Ident],
81) -> syn::ItemImpl {
82 let default = &variants[0].ident;
83
84 let arms: Vec<syn::Arm> = variants
85 .iter()
86 .zip(idents)
87 .map(|(var, id)| {
88 let v_id = &var.ident;
89 if let Some((_, ref expr)) = var.discriminant {
90 parse_quote!(#expr => #enum_name::#v_id,)
92 } else {
93 parse_quote!(x if x == pam_sys::#id => #enum_name::#v_id,)
96 }
97 })
98 .collect();
99
100 parse_quote! {
101 impl std::convert::From<i32> for #enum_name {
102 fn from(value: i32) -> Self {
103 match value {
104 #(#arms)*
105 _ => #enum_name::#default
106 }
107 }
108 }
109 }
110}
111
112fn prefix_ident(ident: &syn::Ident, prefix: &str) -> syn::Ident {
113 syn::Ident::new(
114 &format!("{}{}", prefix, ident.to_string().to_uppercase()),
115 ident.span(),
116 )
117}