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}