pam_macros/
lib.rs

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    // Match only enums
13    let mut def = match item {
14        syn::Item::Enum(ie) => ie,
15        _ => {
16            // FIXME: slightly clunky
17            return TokenStream::from(
18                syn::Error::new_spanned(item, "[pam_enum] only works on enums").to_compile_error(),
19            );
20        }
21    };
22
23    // Clone original variants and create prefixed idents as they are used multiple times
24    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    // Attach additional derives to enum definition
32    def.attrs.extend(derive_attrs());
33
34    // Build enum variants from uppercased identifiers in pam_sys::
35    def.variants = build_variants(&variants, &idents);
36
37    // Build additional impl block for From<i32>
38    let impl_block = build_impl_block(&def.ident, &variants, &idents);
39
40    // Assemble the final TokenStream
41    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            // Only insert our discriminant if none was provided
69            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                // If we have an original expression for the variant, then use it..
91                parse_quote!(#expr => #enum_name::#v_id,)
92            } else {
93                // otherwise, fallback to pam_sys
94                // FIXME: This guard should not be necessary
95                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}