gribberish_macros/
lib.rs

1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4use quote::quote;
5use syn::{parse_macro_input, Item, ItemEnum, DeriveInput};
6
7#[proc_macro_derive(DisplayDescription, attributes(description))]
8pub fn display_description(input: TokenStream) -> TokenStream {
9    // Parse the input tokens into a syntax tree
10    let input = parse_macro_input!(input as DeriveInput);
11    let item: Item = input.into();
12
13    if let Item::Enum(e) = item {
14        // Build the output, possibly using quasi-quotation
15        let expanded = generate_display_impl(&e);
16
17        // Hand the output tokens back to the compiler
18        TokenStream::from(expanded)
19    } else {
20        panic!("Only Enums are supported for DisplayDescription!");
21    }
22}
23
24fn generate_display_impl(enum_data: &ItemEnum) -> TokenStream {
25    let name: &syn::Ident = &enum_data.ident;
26    let variants: &syn::punctuated::Punctuated<syn::Variant, syn::token::Comma> = &enum_data.variants;
27    let variant_names = variants.into_iter().map(|v| v.ident.clone());
28    let variant_descriptions = variants
29        .into_iter()
30        .map(|v| {
31            let desc_attribute = v.attrs.iter().find(|a| a.path.is_ident("description"));
32            match desc_attribute {
33                Some(a) => a.tokens.to_string().replace("=", "").replace("\"", "").trim().to_string(),
34                _ => v.ident.to_string().to_lowercase(),
35            }
36        });
37
38    (quote! {
39        impl std::fmt::Display for #name {
40            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41                let description = match self {
42                    #(
43                        #name::#variant_names => #variant_descriptions,
44                    )*
45                };
46                write!(f, "{}", description)
47            }
48        }
49    }).into()
50}
51
52#[proc_macro_derive(FromValue)]
53pub fn from_value(input: TokenStream) -> TokenStream {
54    // Parse the input tokens into a syntax tree
55    let input = parse_macro_input!(input as DeriveInput);
56    let item: Item = input.into();
57
58    if let Item::Enum(e) = item {
59        // Build the output, possibly using quasi-quotation
60        let expanded = generate_from_value_impl(&e);
61
62        // Hand the output tokens back to the compiler
63        TokenStream::from(expanded)
64    } else {
65        panic!("Only Enums are supported for DisplayDescription!");
66    }
67}
68
69fn generate_from_value_impl(enum_data: &ItemEnum) -> TokenStream {
70    let name: &syn::Ident = &enum_data.ident;
71    let variants: &syn::punctuated::Punctuated<syn::Variant, syn::token::Comma> = &enum_data.variants;
72    let variant_names = variants.into_iter().map(|v| v.ident.clone());
73    let default_variant_name = variant_names.clone().last().clone().unwrap();
74    let variant_values = variants.into_iter().map(|v| match &v.discriminant {
75        Some((_, expr)) => match expr {
76            syn::Expr::Lit(value) => match &value.lit {
77                syn::Lit::Int(i) => i.base10_parse().unwrap_or(254u8),
78                _ => 253u8,
79            },
80            _ => 252u8
81        },
82        None => 251u8,
83    });
84
85    (quote! {
86        impl std::convert::From<u8> for #name {
87            fn from(value: u8) -> Self {
88                match value {
89                    #(
90                        #variant_values => #name::#variant_names,
91                    )*
92                    _ => #name::#default_variant_name
93                }
94            }
95        }
96    }).into()
97}
98
99#[proc_macro_derive(ToParameter, attributes(name, abbrev, unit))]
100pub fn parameter_attributes(input: TokenStream) -> TokenStream {
101    // Parse the input tokens into a syntax tree
102    let input = parse_macro_input!(input as DeriveInput);
103    let item: Item = input.into();
104
105    if let Item::Enum(e) = item {
106        // Build the output, possibly using quasi-quotation
107        let expanded = generate_parameter_attributes(&e);
108
109        // Hand the output tokens back to the compiler
110        TokenStream::from(expanded)
111    } else {
112        panic!("Only Enums are supported for DisplayDescription!");
113    }
114}
115
116fn generate_parameter_attributes(enum_data: &ItemEnum) -> TokenStream {
117    let name: &syn::Ident = &enum_data.ident;
118    let variants: &syn::punctuated::Punctuated<syn::Variant, syn::token::Comma> = &enum_data.variants;
119    let variant_names_first = variants.into_iter().map(|v| v.ident.clone());
120    let variant_names_second = variants.into_iter().map(|v| v.ident.clone());
121    let variant_names_third = variants.into_iter().map(|v| v.ident.clone());
122    let variant_names = variants
123        .into_iter()
124        .map(|v| {
125            let unit_attribute = v.attrs.iter().find(|a| a.path.is_ident("name"));
126            match unit_attribute {
127                Some(a) => a.tokens.to_string().replace("=", "").replace("\"", "").trim().to_string(),
128                _ => v.ident.to_string().to_lowercase(),
129            }
130        });
131    let variant_abbreviations = variants
132        .into_iter()
133        .map(|v| {
134            let abbrev_attribute = v.attrs.iter().find(|a| a.path.is_ident("abbrev"));
135            match abbrev_attribute {
136                Some(a) => a.tokens.to_string().replace("=", "").replace("\"", "").trim().to_string(),
137                _ => v.ident.to_string().to_lowercase(),
138            }
139        });
140    let variant_units = variants
141        .into_iter()
142        .map(|v| {
143            let unit_attribute = v.attrs.iter().find(|a| a.path.is_ident("unit"));
144            match unit_attribute {
145                Some(a) => a.tokens.to_string().replace("=", "").replace("\"", "").trim().to_string(),
146                _ => "".to_string(),
147            }
148        });
149
150    (quote! {
151        impl #name {
152            pub fn name(&self) -> &str {
153                match self {
154                    #(
155                        #name::#variant_names_first => #variant_names,
156                    )*
157                }
158            }
159
160            pub fn abbrev(&self) -> &str {
161                match self {
162                    #(
163                        #name::#variant_names_second => #variant_abbreviations,
164                    )*
165                }
166            }
167
168            pub fn unit(&self) -> &str {
169                match self {
170                    #(
171                        #name::#variant_names_third => #variant_units,
172                    )*
173                }
174            }
175        }
176
177        impl std::convert::From<#name> for Parameter {
178            fn from(value: #name) -> Parameter {
179                Parameter {
180                    name: value.name().to_string(),
181                    unit: value.unit().to_string(),
182                    abbrev: value.abbrev().to_string(),
183                }
184            }
185        }
186    }).into()
187}