asterix_derive/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{parse_macro_input, Data, DeriveInput, Fields};
4
5/// Generate syntax for updating the fspec from the deku style syntax that would be found
6/// in a ASTERIX category
7///
8/// This is mostly a `hack` in the true sense of the word. Although it works pretty well for
9/// the well defined deku derives.
10///
11/// Input:
12/// ```rust, ignore
13/// use asterix::data_item::*;
14/// #[deku(skip, cond = "is_fspec(DataSourceIdentifier::FRN_48, fspec, 0)")]
15/// pub data_source_identifier: Option<DataSourceIdentifier>,
16/// ```
17///
18/// General Output:
19/// ```rust, ignore
20/// use asterix::data_item::*;
21/// if self.data_source_identifier.is_some() {
22///     fspec[0] |= DataSourceIdentifier::FRN_48;
23/// }
24///
25/// ```
26///
27/// There are a few parts that are pre-pended and appended to the end of the above statements, with
28/// generation for the vec and cleaning up the fspec.
29#[proc_macro_derive(UpdateFspec)]
30#[doc(hidden)]
31pub fn derive_answer_fn(input: TokenStream) -> TokenStream {
32    // Parse the input tokens into a syntax tree
33    let input = parse_macro_input!(input as DeriveInput);
34
35    let name = &input.ident; // struct name
36
37    // (self.name, fspec_num, FRN)
38    let mut data_items: Vec<(String, String, String)> = vec![];
39
40    if let Data::Struct(s) = input.data {
41        if let Fields::Named(f) = s.fields {
42            for field in f.named.iter() {
43                let ident = field.ident.as_ref().unwrap(); // they are 'named' fields
44                                                           // check if is first 'fspec' field in struct, skip
45                if ident == "fspec" {
46                    continue;
47                }
48                for attr in &field.attrs {
49                    // check if doc ident, we don't need that one
50                    if !attr.path().is_ident("deku") {
51                        continue;
52                    }
53                    // ident should be 'deku' at this point
54                    // pulling out the `TokenStream` from `Meta::List` and parsing
55                    attr.parse_nested_meta(|meta| {
56                        if meta.path.is_ident("cond") {
57                            let value = meta.value()?; // this parses the `=`
58                            let token: syn::LitStr = value.parse()?; // this parses `"is_fspec(...)"`
59                            let fn_call = token.parse::<syn::ExprCall>().unwrap();
60                            let frn = if let syn::Expr::Path(attrs) = &fn_call.args[0] {
61                                format!(
62                                    "{}::{}",
63                                    attrs.path.segments[0].ident, attrs.path.segments[1].ident,
64                                )
65                            } else {
66                                unreachable!()
67                            };
68
69                            let fspec_num = if let syn::Expr::Lit(lit) = &fn_call.args[2] {
70                                if let syn::Lit::Int(int) = &lit.lit {
71                                    int.to_string()
72                                } else {
73                                    unreachable!();
74                                }
75                            } else {
76                                unreachable!();
77                            };
78                            data_items.push((
79                                ident.to_string(),
80                                fspec_num.to_string(),
81                                frn.to_string(),
82                            ));
83                        }
84                        Ok(())
85                    })
86                    .expect("Error parsing nested meta");
87                }
88            }
89        }
90    }
91
92    let mut quotes = quote! {};
93
94    for data_item in data_items {
95        let ident = syn::Ident::new(&data_item.0.to_string(), proc_macro2::Span::call_site());
96        let fspec_num = data_item.1.parse::<usize>().unwrap();
97        let frn = data_item.2;
98        let frn = syn::parse_str::<syn::Expr>(&frn).unwrap();
99        quotes = quote! {
100            #quotes
101            if self.#ident.is_some() {
102                fspec[#fspec_num] |= #frn;
103            }
104        }
105    }
106
107    let expanded = quote! {
108        impl #name {
109            pub fn update_fspec(&mut self) {
110                let mut fspec = vec![0x00; 10];
111                #quotes
112                trim_fspec(&mut fspec);
113                add_fx(&mut fspec);
114                self.fspec = fspec;
115            }
116        }
117    };
118    // Hand the output tokens back to the compiler
119    TokenStream::from(expanded) // also could be 'expanded.into()'
120}