blob: 549c609eb8506456fd6f88d26e06edf908e1af47 [file] [log] [blame]
extern crate proc_macro;
use quote::quote;
use std::fs::File;
use std::io::Write;
use std::path::Path;
use syn::parse::Parser;
use syn::punctuated::Punctuated;
use syn::token::Comma;
use syn::{Expr, FnArg, ItemTrait, Meta, Pat, TraitItem};
use crate::proc_macro::TokenStream;
fn debug_output_to_file(gen: &proc_macro2::TokenStream, filename: String) {
let path = Path::new(filename.as_str());
let mut file = File::create(&path).unwrap();
file.write_all(gen.to_string().as_bytes()).unwrap();
}
/// Associates a function with a btif callback message.
#[proc_macro_attribute]
pub fn btif_callback(_attr: TokenStream, item: TokenStream) -> TokenStream {
let ori_item: proc_macro2::TokenStream = item.clone().into();
let gen = quote! {
#ori_item
};
gen.into()
}
/// Generates a dispatcher from a message to a function.
#[proc_macro_attribute]
pub fn btif_callbacks_dispatcher(attr: TokenStream, item: TokenStream) -> TokenStream {
let args = Punctuated::<Expr, Comma>::parse_separated_nonempty.parse(attr.clone()).unwrap();
let struct_ident = if let Expr::Path(p) = &args[0] {
p.path.get_ident().unwrap()
} else {
panic!("struct name must be specified");
};
let fn_ident = if let Expr::Path(p) = &args[1] {
p.path.get_ident().unwrap()
} else {
panic!("function name must be specified");
};
let callbacks_struct_ident = if let Expr::Path(p) = &args[2] {
p.path.get_ident().unwrap()
} else {
panic!("callbacks struct ident must be specified");
};
let mut dispatch_arms = quote! {};
let ast: ItemTrait = syn::parse(item.clone()).unwrap();
let mut fn_names = quote! {};
for attr in ast.items {
if let TraitItem::Method(m) = attr {
if m.attrs.len() != 1 {
continue;
}
let attr = &m.attrs[0];
if !attr.path.get_ident().unwrap().to_string().eq("btif_callback") {
continue;
}
let attr_args = attr.parse_meta().unwrap();
let btif_callback = if let Meta::List(meta_list) = attr_args {
Some(meta_list.nested[0].clone())
} else {
None
};
if btif_callback.is_none() {
continue;
}
let mut arg_names = quote! {};
for input in m.sig.inputs {
if let FnArg::Typed(t) = input {
if let Pat::Ident(i) = *t.pat {
let attr_name = i.ident;
arg_names = quote! { #arg_names #attr_name, };
}
}
}
let method_ident = m.sig.ident;
fn_names = quote! {
#fn_names
#method_ident,
};
dispatch_arms = quote! {
#dispatch_arms
#callbacks_struct_ident::#btif_callback(#arg_names) => {
self.#method_ident(#arg_names);
}
};
}
}
let ori_item = proc_macro2::TokenStream::from(item.clone());
let gen = quote! {
#ori_item
impl #struct_ident {
pub(crate) fn #fn_ident(&mut self, cb: #callbacks_struct_ident) {
match cb {
#dispatch_arms
_ => println!("Unhandled callback arm {:?}", cb),
}
}
}
};
// TODO: Have a simple framework to turn on/off macro-generated code debug.
debug_output_to_file(&gen, format!("/tmp/out-{}.rs", fn_ident.to_string()));
gen.into()
}