1 extern crate proc_macro;
2 
3 use quote::quote;
4 
5 use std::fs::File;
6 use std::io::Write;
7 use std::path::Path;
8 
9 use syn::parse::Parser;
10 use syn::punctuated::Punctuated;
11 use syn::token::Comma;
12 use syn::{Expr, FnArg, ItemTrait, Meta, Pat, TraitItem};
13 
14 use crate::proc_macro::TokenStream;
15 
16 const OUTPUT_DEBUG: bool = false;
17 
debug_output_to_file(gen: &proc_macro2::TokenStream, filename: String)18 fn debug_output_to_file(gen: &proc_macro2::TokenStream, filename: String) {
19     if !OUTPUT_DEBUG {
20         return;
21     }
22 
23     let filepath = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap())
24         .join(filename)
25         .to_str()
26         .unwrap()
27         .to_string();
28 
29     let path = Path::new(&filepath);
30     let mut file = File::create(&path).unwrap();
31     file.write_all(gen.to_string().as_bytes()).unwrap();
32 }
33 
34 /// Associates a function with a btif callback message.
35 #[proc_macro_attribute]
btif_callback(_attr: TokenStream, item: TokenStream) -> TokenStream36 pub fn btif_callback(_attr: TokenStream, item: TokenStream) -> TokenStream {
37     let ori_item: proc_macro2::TokenStream = item.clone().into();
38     let gen = quote! {
39         #ori_item
40     };
41     gen.into()
42 }
43 
44 /// Generates a dispatcher from a message to a function.
45 ///
46 /// Example usage: This will generate a function called `dispatch_base_callbacks` to dispatch
47 /// `bt_topshim::btif::BaseCallbacks` to the functions in the defined trait.
48 ///
49 /// ```ignore
50 /// #[btif_callbacks_dispatcher(dispatch_base_callbacks, BaseCallbacks)]
51 /// trait BtifBluetoothCallbacks {
52 ///     #[btif_callback(Foo)]
53 ///     fn foo(&mut self, param1: u32, param2: bool);
54 ///     #[btif_callback(Bar)]
55 ///     fn bar(&mut self);
56 /// }
57 /// ```
58 ///
59 /// Structs can implement the callback trait like:
60 /// ```ignore
61 /// struct Struct1 {}
62 /// impl BtifBluetoothCallbacks for Struct1 {
63 ///     fn foo(&mut self, param1: u32, param2: bool) {...}
64 ///     fn bar(&mut self) {...}
65 /// }
66 ///
67 /// struct Struct2 {}
68 /// impl BtifBluetoothCallbacks for Struct2 {
69 ///     fn foo(&mut self, param1: u32, param2: bool) {...}
70 ///     fn bar(&mut self) {...}
71 /// }
72 /// ```
73 ///
74 /// The generated function can be called against any struct that implements the defined trait:
75 /// ```ignore
76 /// let struct1 = Struct1 {};
77 /// let struct2 = Struct2 {};
78 /// dispatch_base_callbacks(&mut struct1, BaseCallbacks::Foo(1, true));
79 /// dispatch_base_callbacks(&mut struct2, BaseCallbacks::Foo(2, false));
80 /// ```
81 #[proc_macro_attribute]
btif_callbacks_dispatcher(attr: TokenStream, item: TokenStream) -> TokenStream82 pub fn btif_callbacks_dispatcher(attr: TokenStream, item: TokenStream) -> TokenStream {
83     let args = Punctuated::<Expr, Comma>::parse_separated_nonempty.parse(attr.clone()).unwrap();
84 
85     let fn_ident = if let Expr::Path(p) = &args[0] {
86         p.path.get_ident().unwrap()
87     } else {
88         panic!("function name must be specified");
89     };
90 
91     let callbacks_struct_ident = if let Expr::Path(p) = &args[1] {
92         p.path.get_ident().unwrap()
93     } else {
94         panic!("callbacks struct ident must be specified");
95     };
96 
97     let mut dispatch_arms = quote! {};
98 
99     let ast: ItemTrait = syn::parse(item.clone()).unwrap();
100     let trait_ident = ast.ident;
101 
102     let mut fn_names = quote! {};
103     for attr in ast.items {
104         if let TraitItem::Method(m) = attr {
105             if m.attrs.len() != 1 {
106                 continue;
107             }
108 
109             let attr = &m.attrs[0];
110             if !attr.path.get_ident().unwrap().to_string().eq("btif_callback") {
111                 continue;
112             }
113 
114             let attr_args = attr.parse_meta().unwrap();
115             let btif_callback = if let Meta::List(meta_list) = attr_args {
116                 Some(meta_list.nested[0].clone())
117             } else {
118                 None
119             };
120 
121             if btif_callback.is_none() {
122                 continue;
123             }
124 
125             let mut arg_names = quote! {};
126             for input in m.sig.inputs {
127                 if let FnArg::Typed(t) = input {
128                     if let Pat::Ident(i) = *t.pat {
129                         let attr_name = i.ident;
130                         arg_names = quote! { #arg_names #attr_name, };
131                     }
132                 }
133             }
134             let method_ident = m.sig.ident;
135 
136             fn_names = quote! {
137                 #fn_names
138                 #method_ident,
139             };
140 
141             dispatch_arms = quote! {
142                 #dispatch_arms
143                 #callbacks_struct_ident::#btif_callback(#arg_names) => {
144                     obj.#method_ident(#arg_names);
145                 }
146             };
147         }
148     }
149 
150     let ori_item = proc_macro2::TokenStream::from(item.clone());
151 
152     let gen = quote! {
153         #ori_item
154         pub(crate) fn #fn_ident<T: #trait_ident>(obj: &mut T, cb: #callbacks_struct_ident) {
155             match cb {
156                 #dispatch_arms
157 
158                 _ => println!("Unhandled callback arm {:?}", cb),
159             }
160         }
161     };
162 
163     // TODO: Have a simple framework to turn on/off macro-generated code debug.
164     debug_output_to_file(&gen, format!("out-{}.rs", fn_ident.to_string()));
165 
166     gen.into()
167 }
168