blaze_pk_derive/
lib.rs

1use darling::FromAttributes;
2use proc_macro::TokenStream;
3use quote::quote;
4use syn::{
5    parse_macro_input, punctuated::Punctuated, token::Comma, Data, DataEnum, DeriveInput, Field,
6    Fields, Ident, 
7};
8
9/// Options for a component field on the components enum
10#[derive(FromAttributes)]
11#[darling(attributes(component), forward_attrs(allow, doc, cfg))]
12struct ComponentOpts {
13    /// The component target value
14    target: u16,
15}
16
17/// Macro for deriving components any enum that wants to implement
18/// PacketComponents must also implement Debug, Hash, PartialEq, and Eq
19/// these traits are required for routing
20///
21/// ```
22/// use blaze_pk::{PacketComponents, PacketComponent}
23///
24/// #[derive(Debug, Hash, PartialEq, Eq, PacketComponents)]
25/// pub enum Components {
26///     #[component(target = 0x1)]
27///     Component1(Component1)
28/// }
29///
30/// #[derive(Debug, Hash, PartialEq, Eq, PacketComponents)]
31/// pub enum Component1 {
32///     #[command(target = 0x14)]
33///     Value,
34///     #[command(target = 0x14, notify)]
35///     NotifyValue,
36/// }
37///
38/// ```
39#[proc_macro_derive(PacketComponents, attributes(component))]
40pub fn derive_componets(input: TokenStream) -> TokenStream {
41    let input: DeriveInput = parse_macro_input!(input);
42    let ident: Ident = input.ident;
43
44    // PacketComponents can only be enum types
45    let data: DataEnum = match input.data {
46        Data::Enum(data) => data,
47        ty => panic!(
48            "Expects enum for components derive dont know how to handle: {:?}",
49            ty
50        ),
51    };
52
53    let length = data.variants.len();
54    let mut values = Vec::with_capacity(length);
55    let mut from_values = Vec::with_capacity(length);
56
57    for variant in data.variants {
58        let name: Ident = variant.ident;
59
60        // Parse the component attributes
61        let target: u16 = match ComponentOpts::from_attributes(&variant.attrs) {
62            Ok(value) => value.target,
63            Err(err) => panic!("Unable to parse attributes for field '{}': {:?}", name, err),
64        };
65
66        // Ensure we only have one un-named field on the enum variant
67        let mut fields: Punctuated<Field, Comma> = match variant.fields {
68            Fields::Unnamed(fields) => fields.unnamed,
69            _ => panic!("Field on '{}' must be unnamed and not unit type", name),
70        };
71        if fields.len() != 1 {
72            panic!("Expected only 1 field on '{}' for component value", name);
73        }
74
75        // Take the enum field and its type
76        let value = fields
77            .pop()
78            .expect("Expected one component type value")
79            .into_value();
80
81        let ty = value.ty;
82
83        // Create the mappings for the values match
84        values.push(quote! { Self::#name(value) => (#target, value.command()), });
85        // Create the mappings for the from_values match
86        from_values
87            .push(quote! { #target => Some(Self::#name(#ty::from_value(command, notify)?)), });
88    }
89
90    // Implement the trait
91    quote! {
92        impl blaze_pk::packet::PacketComponents for #ident {
93
94            fn values(&self) -> (u16, u16) {
95                use blaze_pk::packet::PacketComponent;
96                match self {
97                    #(#values)*
98                }
99            }
100
101            fn from_values(component: u16, command: u16, notify: bool) -> Option<Self> {
102                use blaze_pk::packet::PacketComponent;
103                match component {
104                    #(#from_values)*
105                    _ => None
106                }
107            }
108        }
109    }
110    .into()
111}
112
113/// Options for a command field on a component
114#[derive(FromAttributes)]
115#[darling(attributes(command), forward_attrs(allow, doc, cfg))]
116struct CommandOpts {
117    /// The command target value
118    target: u16,
119    /// Whether the command is a notify type
120    #[darling(default)]
121    notify: bool,
122}
123
124/// Macro for deriving a component any enum that wants to implement
125/// PacketComponent must also implement Debug, Hash, PartialEq, and Eq
126/// these traits are required for routing
127///
128/// ```
129/// use blaze_pk::{PacketComponent}
130///
131/// #[derive(Debug, Hash, PartialEq, Eq, PacketComponents)]
132/// pub enum Component1 {
133///     #[command(target = 0x14)]
134///     Value,
135///     #[command(target = 0x14, notify)]
136///     NotifyValue,
137/// }
138///
139/// ```
140#[proc_macro_derive(PacketComponent, attributes(command))]
141pub fn derive_component(input: TokenStream) -> TokenStream {
142    let input: DeriveInput = parse_macro_input!(input);
143    let ident: Ident = input.ident;
144
145    let data: DataEnum = match input.data {
146        Data::Enum(data) => data,
147        ty => panic!(
148            "Expects enum for component derive dont know how to handle: {:?}",
149            ty
150        ),
151    };
152
153    let length = data.variants.len();
154
155    let mut from_notify_value = Vec::new();
156    let mut from_normal_value = Vec::new();
157
158    let mut command = Vec::with_capacity(length);
159
160    for variant in data.variants {
161        let name: Ident = variant.ident;
162        let CommandOpts { target, notify } = match CommandOpts::from_attributes(&variant.attrs) {
163            Ok(value) => value,
164            Err(err) => panic!(
165                "Unable to parse component options for field '{}': {:?}",
166                name, err
167            ),
168        };
169
170        command.push(quote! { Self::#name => #target, });
171
172        let list = if notify {
173            &mut from_notify_value
174        } else {
175            &mut from_normal_value
176        };
177
178        list.push(quote! { #target => Some(Self::#name), })
179    }
180
181    let from_value_notify = if from_notify_value.is_empty() {
182        quote!(None)
183    } else {
184        quote! {
185            match value {
186                #(#from_notify_value)*
187                _ => None
188            }
189        }
190    };
191
192    let from_value_normal = if from_normal_value.is_empty() {
193        quote!(None)
194    } else {
195        quote! {
196            match value {
197                #(#from_normal_value)*
198                _ => None
199            }
200        }
201    };
202
203    // Implement PacketComponent
204    quote! {
205        impl blaze_pk::packet::PacketComponent for #ident {
206            fn command(&self) -> u16 {
207                match self {
208                    #(#command)*
209                }
210            }
211
212            fn from_value(value: u16, notify: bool) -> Option<Self> {
213                if notify {
214                    #from_value_notify
215                } else {
216                    #from_value_normal
217                }
218
219            }
220        }
221    }
222    .into()
223}