locustdb_derive/
enum_syntax.rs

1use super::proc_macro::TokenStream;
2use syn::*;
3use proc_macro2::Span;
4
5pub fn enum_syntax(input: TokenStream) -> TokenStream {
6    // Parse the input tokens into a syntax tree
7    let input = parse_macro_input!(input as DeriveInput);
8
9
10    if let Data::Enum(DataEnum { variants, .. }) = input.data {
11        let enum_ident = input.ident;
12        let mut constructors = Vec::<Item>::new();
13        let boxed: Type = parse_quote!(Box<#enum_ident>);
14        let string: Type = parse_quote!(String);
15        for variant in variants.into_iter() {
16            if let Fields::Named(fields) = variant.fields {
17                let variant_ident = variant.ident.clone();
18                let ident_snake = studley_to_snake(variant.ident);
19                let mut fn_inputs = Vec::<FnArg>::new();
20                let mut struct_args = Vec::<FieldValue>::new();
21                let mut fn_generics = Vec::<GenericArgument>::new();
22                for field in fields.named.into_iter() {
23                    let field_ident = field.ident.clone().unwrap();
24                    if field.ty == boxed {
25                        let type_ident = Ident::new(&format!("_T{}", fn_generics.len()), Span::call_site());
26                        fn_generics.push(parse_quote!(#type_ident: Into<#enum_ident>));
27                        fn_inputs.push(parse_quote!(#field_ident: #type_ident));
28                        struct_args.push(parse_quote!(#field_ident: Box::new(#field_ident.into())));
29                    } else if field.ty == string {
30                        fn_inputs.push(parse_quote!(#field_ident: &str));
31                        struct_args.push(parse_quote!(#field_ident: #field_ident.to_string()));
32                    } else {
33                        fn_inputs.push(parse_quote!(#field));
34                        struct_args.push(parse_quote!(#field_ident));
35                    }
36                }
37
38                let item = parse_quote! {
39                    pub fn #ident_snake <#(#fn_generics),*> ( #(#fn_inputs),* ) -> #enum_ident {
40                        #enum_ident::#variant_ident { #(#struct_args),* }
41                    }
42                };
43                constructors.push(item);
44            }
45        }
46        let expanded = quote! {
47            pub mod syntax {
48                use super::*;
49
50                #(#constructors)*
51            }
52        };
53
54        // Hand the output tokens back to the compiler
55        TokenStream::from(expanded)
56    } else {
57        Span::call_site().unstable().error(format!("EnumSyntax must be applied to an enum"));
58        TokenStream::from(quote!())
59    }
60}
61
62fn studley_to_snake(ident: Ident) -> Ident {
63    let mut snake_case = String::new();
64    let mut previous_lowercase = false;
65    for c in format!("{}", ident).chars() {
66        if c.is_uppercase() {
67            if previous_lowercase {
68                snake_case.push('_');
69            }
70            previous_lowercase = false;
71            for l in c.to_lowercase() {
72                snake_case.push(l);
73            }
74        } else {
75            previous_lowercase = true;
76            snake_case.push(c);
77        }
78    }
79    Ident::new(&snake_case, ident.span())
80}