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
debug_output_to_file(gen: &proc_macro2::TokenStream, filename: String)16 fn debug_output_to_file(gen: &proc_macro2::TokenStream, filename: String) {
17 let path = Path::new(filename.as_str());
18 let mut file = File::create(&path).unwrap();
19 file.write_all(gen.to_string().as_bytes()).unwrap();
20 }
21
22 /// Specifies the `Stack::Message` associated with a topshim callback.
23 #[proc_macro_attribute]
stack_message(_attr: TokenStream, item: TokenStream) -> TokenStream24 pub fn stack_message(_attr: TokenStream, item: TokenStream) -> TokenStream {
25 let ori_item: proc_macro2::TokenStream = item.clone().into();
26 let gen = quote! {
27 #[allow(unused_variables)]
28 #ori_item
29 };
30 gen.into()
31 }
32
33 /// Generates a topshim callback object that contains closures.
34 ///
35 /// The closures are generated to be calls to the corresponding `Stack::Message`.
36 #[proc_macro_attribute]
btif_callbacks_generator(attr: TokenStream, item: TokenStream) -> TokenStream37 pub fn btif_callbacks_generator(attr: TokenStream, item: TokenStream) -> TokenStream {
38 let args = Punctuated::<Expr, Comma>::parse_separated_nonempty.parse(attr.clone()).unwrap();
39
40 let fn_ident = if let Expr::Path(p) = &args[0] {
41 p.path.get_ident().unwrap()
42 } else {
43 panic!("function name must be specified");
44 };
45
46 let callbacks_struct_ident = if let Expr::Path(p) = &args[1] {
47 p.path.get_ident().unwrap()
48 } else {
49 panic!("callbacks struct ident must be specified");
50 };
51
52 let ast: ItemTrait = syn::parse(item.clone()).unwrap();
53
54 let mut fn_names = quote! {};
55 let mut closure_defs = quote! {};
56 for attr in ast.items {
57 if let TraitItem::Method(m) = attr {
58 if m.attrs.len() != 1 {
59 continue;
60 }
61
62 let attr = &m.attrs[0];
63 if !attr.path.get_ident().unwrap().to_string().eq("stack_message") {
64 continue;
65 }
66
67 let attr_args = attr.parse_meta().unwrap();
68 let stack_message = if let Meta::List(meta_list) = attr_args {
69 Some(meta_list.nested[0].clone())
70 } else {
71 None
72 };
73
74 if stack_message.is_none() {
75 continue;
76 }
77
78 let mut arg_names = quote! {};
79 for input in m.sig.inputs {
80 if let FnArg::Typed(t) = input {
81 if let Pat::Ident(i) = *t.pat {
82 let attr_name = i.ident;
83 arg_names = quote! { #arg_names #attr_name, };
84 }
85 }
86 }
87 let method_ident = m.sig.ident;
88
89 fn_names = quote! {
90 #fn_names
91 #method_ident,
92 };
93
94 closure_defs = quote! {
95 #closure_defs
96 let tx_clone = tx.clone();
97 let #method_ident = Box::new(move |#arg_names| {
98 let tx = tx_clone.clone();
99 topstack::get_runtime().spawn(async move {
100 let result = tx.send(Message::#stack_message(#arg_names)).await;
101 if let Err(e) = result {
102 eprintln!("Error in sending message: {}", e);
103 }
104 });
105 });
106 };
107 }
108 }
109
110 let ori_item = proc_macro2::TokenStream::from(item.clone());
111
112 let gen = quote! {
113 #ori_item
114
115 /// Returns a callback object to be passed to topshim.
116 pub fn #fn_ident(tx: tokio::sync::mpsc::Sender<Message>) -> #callbacks_struct_ident {
117 #closure_defs
118 #callbacks_struct_ident {
119 #fn_names
120 // TODO: Handle these in main loop.
121 acl_state_changed: Box::new(|_, _, _, _| {}),
122 bond_state_changed: Box::new(|_, _, _| {}),
123 device_found: Box::new(|_, _| {}),
124 discovery_state_changed: Box::new(|_| {}),
125 pin_request: Box::new(|_, _, _, _| {}),
126 remote_device_properties_changed: Box::new(|_, _, _, _| {}),
127 ssp_request: Box::new(|_, _, _, _, _| {}),
128 }
129 }
130 };
131
132 // TODO: Have a simple framework to turn on/off macro-generated code debug.
133 debug_output_to_file(&gen, format!("/tmp/out-{}.rs", fn_ident.to_string()));
134
135 gen.into()
136 }
137