neli_proc_macros/
neli_enum.rs

1use proc_macro::TokenStream;
2use proc_macro2::{Span, TokenStream as TokenStream2};
3use quote::quote;
4use syn::{
5    Arm, Attribute, Expr, Ident, ItemEnum, Lit, Meta, Path, Token, Type, Variant, parse, parse_str,
6};
7
8use crate::shared::remove_bad_attrs;
9
10fn parse_type_attr(attr: Meta) -> Type {
11    if let Meta::NameValue(nv) = attr {
12        if nv.path == parse_str::<Path>("serialized_type").unwrap() {
13            if let Expr::Lit(el) = nv.value {
14                if let Lit::Str(ls) = el.lit {
15                    return parse_str::<Type>(&ls.value())
16                        .unwrap_or_else(|_| panic!("Invalid type supplied: {}", ls.value()));
17                }
18            }
19        }
20    }
21
22    panic!("Attribute in the form #[neli(serialized_type = \"TYPE_LITERAL_STR\")] required")
23}
24
25fn parse_enum(enm: &mut ItemEnum, ty: &Type) -> Vec<(Vec<Attribute>, Ident, Expr)> {
26    let exprs = enm
27        .variants
28        .iter_mut()
29        .map(|var| {
30            if let Some((_, expr)) = var.discriminant.take() {
31                (var.attrs.clone(), var.ident.clone(), expr)
32            } else {
33                panic!("All variants in the provided enum require an expression assignment")
34            }
35        })
36        .collect();
37    if !enm.variants.trailing_punct() {
38        enm.variants.push_punct(Token![,](Span::call_site()));
39    }
40    enm.variants.push_value(
41        parse::<Variant>(TokenStream::from(quote! {
42            UnrecognizedConst(#ty)
43        }))
44        .expect("Could not parse tokens as a variant"),
45    );
46    exprs
47}
48
49fn parse_from_info(
50    enum_name: Ident,
51    var_info: Vec<(Vec<Attribute>, Ident, Expr)>,
52) -> (Vec<Arm>, Vec<Arm>) {
53    let mut from_const_info = Vec::new();
54    let mut from_type_info = Vec::new();
55    for (mut attributes, ident, expr) in var_info {
56        attributes = remove_bad_attrs(attributes);
57        let mut from_const_arm = parse::<Arm>(TokenStream::from(quote! {
58            #(
59                #attributes
60            )*
61            i if i == #expr => #enum_name::#ident,
62        }))
63        .expect("Failed to parse tokens as a match arm");
64        from_const_arm.attrs = attributes.clone();
65        from_const_info.push(from_const_arm);
66
67        let mut from_type_arm = parse::<Arm>(TokenStream::from(quote! {
68            #(
69                #attributes
70            )*
71            #enum_name::#ident => #expr,
72        }))
73        .expect("Failed to parse tokens as a match arm");
74        from_type_arm.attrs = attributes.clone();
75        from_type_info.push(from_type_arm);
76    }
77    (from_const_info, from_type_info)
78}
79
80pub fn generate_neli_enum(mut enm: ItemEnum, meta: Meta) -> TokenStream2 {
81    let enum_name = enm.ident.clone();
82    let ty = parse_type_attr(meta);
83
84    let variant_info = parse_enum(&mut enm, &ty);
85    let (from_const_info, from_type_info) = parse_from_info(enum_name.clone(), variant_info);
86
87    quote! {
88        #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
89        #[allow(missing_docs)]
90        #enm
91
92        impl #enum_name {
93            /// Check whether a given method is an unrecognized
94            /// constant for the set of possible constants
95            /// associated with the current type.
96            pub fn is_unrecognized(&self) -> bool {
97                match *self {
98                    #enum_name::UnrecognizedConst(_) => true,
99                    _ => false,
100                }
101            }
102        }
103
104        impl neli::Size for #enum_name {
105            fn unpadded_size(&self) -> usize {
106                std::mem::size_of::<#ty>()
107            }
108        }
109
110        impl neli::TypeSize for #enum_name {
111            fn type_size() -> usize {
112                std::mem::size_of::<#ty>()
113            }
114        }
115
116        impl neli::ToBytes for #enum_name {
117            fn to_bytes(&self, buffer: &mut std::io::Cursor<Vec<u8>>) -> Result<(), neli::err::SerError> {
118                let bin_rep: #ty = self.into();
119                bin_rep.to_bytes(buffer)
120            }
121        }
122
123        impl neli::FromBytes for #enum_name {
124            fn from_bytes(buffer: &mut std::io::Cursor<impl AsRef<[u8]>>) -> Result<Self, neli::err::DeError> {
125                Ok(#enum_name::from(<#ty as neli::FromBytes>::from_bytes(
126                    buffer
127                )?))
128            }
129        }
130
131        impl From<#ty> for #enum_name {
132            fn from(cnst: #ty) -> Self {
133                match cnst {
134                    #(
135                        #from_const_info
136                    )*
137                    i => #enum_name::UnrecognizedConst(i),
138                }
139            }
140        }
141
142        impl From<#enum_name> for #ty {
143            fn from(enm: #enum_name) -> Self {
144                match enm {
145                    #(
146                        #from_type_info
147                    )*
148                    #enum_name::UnrecognizedConst(i) => i,
149                }
150            }
151        }
152
153        impl From<&#enum_name> for #ty {
154            fn from(enm: &#enum_name) -> Self {
155                match *enm {
156                    #(
157                        #from_type_info
158                    )*
159                    #enum_name::UnrecognizedConst(i) => i,
160                }
161            }
162        }
163    }
164}