extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::{quote, quote_spanned};
use syn::{
parse_macro_input,
Expr,
FnArg,
ForeignItem,
ForeignItemFn,
Ident,
ItemForeignMod,
PatType,
ReturnType,
Type,
punctuated::Punctuated,
spanned::Spanned,
token::Comma,
};
fn function_is_whitelisted(fname: &str) -> bool {
if fname.starts_with("lv_") { return true; }
match fname { "os_eventq_dflt_get" => { true }
"os_eventq_run" => { true }
"os_task_init" => { true }
"sensor_mgr_find_next_bydevname" => { true }
"sensor_read" => { true }
"sensor_set_poll_rate_ms" => { true }
"do_server_post" => { true }
"get_device_id" => { true }
"init_server_post" => { true }
"sensor_network_prepare_post" => { true }
"start_server_transport" => { true }
_ => { false } }
}
fn get_namespace(fname: &str) -> String {
if fname.starts_with("lv_") { return get_namespace_lvgl(fname); }
let fname_split: Vec<&str> = fname.splitn(2, "_").collect();
let namespace = fname_split[0];
match namespace {
"do" => { "".to_string() } "get" => { "".to_string() } "init" => { "".to_string() } "start" => { "".to_string() } "sensor" => {
if fname.starts_with("sensor_network_") { "sensor_network".to_string() }
else { "sensor".to_string() }
}
_ => { namespace.to_string() } }
}
fn get_namespace_lvgl(fname: &str) -> String {
let fname_split: Vec<&str> = fname.splitn(3, "_").collect();
if fname_split.len() < 3 { return "".to_string(); } let namespace1 = fname_split[0];
let namespace2 = fname_split[1];
match namespace1 {
"lv" => format!("{}_{}", namespace1, namespace2), _ => "".to_string() }
}
pub fn safe_wrap_internal(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as ItemForeignMod);
for foreign_item in input.items { match foreign_item {
ForeignItem::Fn(foreign_fn) => {
let expanded = wrap_function(&foreign_fn);
return TokenStream::from(expanded) }
ForeignItem::Static(foreign_static) => {
return quote! { extern "C" { #foreign_static } }.into()
}
_ => {
println!("safe_wrap_internal: Unknown extern {:#?}", quote! { #foreign_item }.to_string());
assert!(false, "Unknown extern"); }
}
break;
}
println!("safe_wrap_internal: Loop should not terminate");
assert!(false, "Non-function extern"); "// Should not come here".parse().unwrap()
}
pub fn wrap_function(foreign_fn: &ForeignItemFn) -> proc_macro2::TokenStream {
let sig = &foreign_fn.sig; let foreign_item_tokens = quote! { #foreign_fn };
let transformed_fname = transform_function_name(&sig.ident);
let TransformedFunctionName{
ident: fname,
token: fname_token,
without_namespace_token: fname_without_namespace_token, ..
} = transformed_fname;
if !function_is_whitelisted(&fname) {
return quote! { extern "C" { #foreign_item_tokens } }
}
let attrs = &foreign_fn.attrs;
let mut doc_tokens = proc_macro2::TokenStream::new();
for attr in attrs {
let attr_span = attr.span();
let tokens = quote_spanned!(attr_span => #attr);
doc_tokens.extend(tokens);
}
let transformed_ret = transform_return_type(&sig.output);
let TransformedReturnType{
declare_result_tokens, get_result_tokens, return_result_tokens, ..
} = transformed_ret;
let args = &sig.inputs;
let transformed_args = transform_arg_list(args);
let wrap_tokens = collect_wrap(&transformed_args);
let validation_tokens = collect_validation(&transformed_args);
let call_tokens = collect_call(&transformed_args);
let expanded = quote! {
#doc_tokens
pub fn #fname_without_namespace_token(
#wrap_tokens
) -> #declare_result_tokens { "----------Insert Extern Decl: `extern C { pub fn ... }`----------";
extern "C" { #foreign_item_tokens }
"----------Insert Validation: `Strn::validate_bytestr(name.bytestr)`----------";
#validation_tokens
unsafe {
"----------Insert Call: `let result_value = os_task_init(`----------";
#get_result_tokens #fname_token(
#call_tokens
);
"----------Insert Result: `Ok(Strn::from_cstr(result_value))`----------";
#return_result_tokens
}
}
};
expanded
}
fn collect_wrap(args: &Vec<TransformedArg>) -> proc_macro2::TokenStream {
let mut expanded = proc_macro2::TokenStream::new();
for arg in args {
let TransformedArg{ ident, wrap_type, ident_span, type_span, .. } = arg;
let tokens = expand_decl(ident, wrap_type, ident_span, type_span, Separator::Colon);
if !expanded.is_empty() { expanded.extend(quote!{ , }) } expanded.extend(tokens);
}
expanded
}
fn collect_call(args: &Vec<TransformedArg>) -> proc_macro2::TokenStream {
let mut expanded = proc_macro2::TokenStream::new();
for arg in args {
let TransformedArg{ call_expr, extern_type, ident_span, type_span, .. } = arg;
let tokens = expand_decl(call_expr, extern_type, ident_span, type_span, Separator::As);
if !expanded.is_empty() { expanded.extend(quote!{ , }) } expanded.extend(tokens);
}
expanded
}
fn collect_validation(args: &Vec<TransformedArg>) -> proc_macro2::TokenStream {
let mut expanded = proc_macro2::TokenStream::new();
for arg in args {
let TransformedArg{ validation_stmt, ident_span, .. } = arg;
if validation_stmt.as_str() == "" { continue; }
let stmt_token = syn::parse_str::<Expr>(validation_stmt).unwrap();
let tokens = quote_spanned!(**ident_span=> #stmt_token);
expanded.extend(quote!{ #tokens ; }); }
expanded
}
fn expand_decl(ident: &str, ty: &str, ident_span: &Span, type_span: &Span, separator: Separator) -> proc_macro2::TokenStream {
let ident_token = syn::parse_str::<Expr>(ident).unwrap();
let type_token = syn::parse_str::<Type>(ty).unwrap();
let ident_token_spanned = quote_spanned!(*ident_span=> #ident_token);
let type_token_spanned = quote_spanned!(*type_span => #type_token);
match separator {
Separator::Colon => { quote!{ #ident_token_spanned : #type_token_spanned } }
Separator::As => { quote!{ #ident_token_spanned as #type_token_spanned } }
}
}
fn transform_arg_list(args: &Punctuated<FnArg, Comma>) -> Vec<TransformedArg>{
let mut res = Vec::new();
for arg in args {
if let FnArg::Typed(arg) = arg {
let arg_transformed = transform_arg(&arg);
res.push(arg_transformed);
}
else { assert!(false, "Unknown arg"); }
}
res
}
fn transform_arg(arg: &PatType) -> TransformedArg {
let syn::PatType { pat, ty, .. } = arg;
let pat_span = pat.span(); let ty_span = ty.span(); let ident = quote!{ #pat }.to_string();
let extern_type = quote!{ #ty }.to_string();
let mut wrap_type = quote!{ #ty }.to_string();
let mut validation_stmt = "".to_string();
let mut call_expr = quote!{ #pat }.to_string();
match extern_type.as_str() {
"* const :: cty :: c_char" => {
wrap_type = "&Strn".to_string();
validation_stmt = format!("{}.validate()", ident);
call_expr = format!("{}.as_ptr()", ident);
}
"* mut :: cty :: c_void" => {
wrap_type = "Ptr".to_string();
}
"* mut os_task" => {
wrap_type = "Out<os_task>".to_string();
}
"* mut os_stack_t" => {
wrap_type = "Out<[os_stack_t]>".to_string();
call_expr = format!("{}.as_ptr()", ident);
}
_ => {}
}
TransformedArg {
ident: Box::new(ident),
extern_type: Box::new(extern_type),
wrap_type: Box::new(wrap_type),
validation_stmt: Box::new(validation_stmt),
call_expr: Box::new(call_expr),
ident_span: Box::new(pat_span),
type_span: Box::new(ty_span),
}
}
fn transform_return_type(output: &ReturnType) -> TransformedReturnType {
let extern_type = quote! { output }.to_string();
let type_span = output.span();
let wrap_type =
if let ReturnType::Type (_, return_type) = output {
(quote! { #return_type }).to_string()
}
else { "".to_string() };
#[cfg(feature = "mynewt_os")] let result_token = quote! { MynewtResult };
#[cfg(feature = "riot_os")] let result_token = quote! { LvglResult };
let declare_result_tokens =
match wrap_type.as_str() {
"" => { quote! { #result_token< () > } }
":: cty :: c_int" => { quote! { #result_token< () > } }
"* const :: cty :: c_char" => { quote! { #result_token< Strn > } }
_ => {
let return_type_tokens = syn::parse_str::<Type>(&wrap_type).unwrap();
quote! { #result_token< #return_type_tokens > }
}
};
let get_result_tokens =
match wrap_type.as_str() {
"" => { quote! {} } _ => { quote! { let result_value = } }
};
let return_result_tokens =
match wrap_type.as_str() {
"" => { quote! { Ok( () ) } }
":: cty :: c_int" => {
quote! {
if result_value == 0 { Ok( () ) }
else { Err( MynewtError::from(result_value) ) }
}
}
"* const :: cty :: c_char" => {
quote! {
Ok( Strn::from_cstr(result_value as *const u8) )
}
}
_ => { quote! { Ok( result_value ) } }
};
TransformedReturnType {
extern_type: Box::new(extern_type),
wrap_type: Box::new(wrap_type),
declare_result_tokens: Box::new(quote_spanned!(type_span=> #declare_result_tokens)),
get_result_tokens: Box::new(quote_spanned!(type_span=> #get_result_tokens)),
return_result_tokens: Box::new(quote_spanned!(type_span=> #return_result_tokens)),
type_span: Box::new(type_span),
}
}
fn transform_function_name(ident: &Ident) -> TransformedFunctionName {
let fname = ident.to_string();
let namespace = get_namespace(&fname);
let namespace_prefix =
if namespace.len() > 0 {
format!("{}_", namespace).to_string() } else {
"".to_string()
};
assert!(fname.starts_with(&namespace_prefix));
let fname_without_namespace = &fname[namespace_prefix.len()..];
let fname_token = Ident::new(&fname, ident.span());
let fname_without_namespace_token = Ident::new(&fname_without_namespace, ident.span());
TransformedFunctionName {
ident: Box::new(fname.clone()),
namespace: Box::new(namespace.to_string()),
without_namespace: Box::new(fname_without_namespace.to_string()),
token: Box::new(fname_token),
without_namespace_token: Box::new(fname_without_namespace_token),
ident_span: Box::new(ident.span()),
}
}
struct TransformedArg {
ident: Box<String>,
extern_type: Box<String>,
wrap_type: Box<String>,
validation_stmt: Box<String>,
call_expr: Box<String>,
ident_span: Box<Span>,
type_span: Box<Span>,
}
#[allow(dead_code)] struct TransformedReturnType {
extern_type: Box<String>,
wrap_type: Box<String>,
declare_result_tokens: Box<proc_macro2::TokenStream>,
get_result_tokens: Box<proc_macro2::TokenStream>,
return_result_tokens: Box<proc_macro2::TokenStream>,
type_span: Box<Span>,
}
#[allow(dead_code)] struct TransformedFunctionName {
ident: Box<String>,
namespace: Box<String>,
without_namespace: Box<String>,
token: Box<Ident>,
without_namespace_token: Box<Ident>,
ident_span: Box<Span>,
}
enum Separator {
Colon,
As,
}