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