use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{spanned::Spanned, Error, Ident, Item, Meta, NestedMeta};
pub(crate) fn export(
attrs: syn::AttributeArgs,
item: TokenStream,
) -> Result<TokenStream2, Error> {
let ExportArgs {
use_wstp,
exported_name,
hidden,
} = parse_export_attribute_args(attrs)?;
let item: Item = syn::parse(item)?;
let func = match item {
Item::Fn(func) => func,
_ => {
return Err(Error::new(
proc_macro2::Span::call_site(),
"this attribute can only be applied to `fn(..) {..}` items",
));
},
};
if let Some(async_) = func.sig.asyncness {
return Err(Error::new(
async_.span(),
"exported function cannot be `async`",
));
}
if let Some(lt) = func.sig.generics.lt_token {
return Err(Error::new(lt.span(), "exported function cannot be generic"));
}
let name = func.sig.ident.clone();
let exported_name: Ident = match exported_name {
Some(name) => name,
None => func.sig.ident.clone(),
};
let params = func.sig.inputs.clone();
let wrapper = if use_wstp {
export_wstp_function(&name, &exported_name, params, hidden)
} else {
export_native_function(&name, &exported_name, params.len(), hidden)
};
let output = quote! {
#func
#wrapper
};
Ok(output)
}
fn export_native_function(
name: &Ident,
exported_name: &Ident,
parameter_count: usize,
hidden: bool,
) -> TokenStream2 {
let params = vec![quote! { _ }; parameter_count];
let mut tokens = quote! {
mod #name {
#[no_mangle]
pub unsafe extern "C" fn #exported_name(
lib: ::wolfram_library_link::sys::WolframLibraryData,
argc: ::wolfram_library_link::sys::mint,
args: *mut ::wolfram_library_link::sys::MArgument,
res: ::wolfram_library_link::sys::MArgument,
) -> std::os::raw::c_uint {
let func: fn(#(#params),*) -> _ = super::#name;
::wolfram_library_link::macro_utils::call_native_wolfram_library_function(
lib,
args,
argc,
res,
func
)
}
}
};
if !hidden {
tokens.extend(quote! {
::wolfram_library_link::inventory::submit! {
::wolfram_library_link::macro_utils::LibraryLinkFunction::Native {
name: stringify!(#exported_name),
signature: || {
let func: fn(#(#params),*) -> _ = #name;
let func: &dyn ::wolfram_library_link::NativeFunction<'_> = &func;
func.signature()
}
}
}
});
}
tokens
}
fn export_wstp_function(
name: &Ident,
exported_name: &Ident,
parameter_tys: syn::punctuated::Punctuated<syn::FnArg, syn::token::Comma>,
hidden: bool,
) -> TokenStream2 {
let mut tokens = quote! {
mod #name {
use super::*;
#[no_mangle]
pub unsafe extern "C" fn #exported_name(
lib: ::wolfram_library_link::sys::WolframLibraryData,
raw_link: ::wolfram_library_link::wstp::sys::WSLINK,
) -> std::os::raw::c_uint {
let func: fn(#parameter_tys) -> _ = super::#name;
::wolfram_library_link::macro_utils::call_wstp_wolfram_library_function(
lib,
raw_link,
func
)
}
}
};
if !hidden {
tokens.extend(quote! {
::wolfram_library_link::inventory::submit! {
::wolfram_library_link::macro_utils::LibraryLinkFunction::Wstp { name: stringify!(#exported_name) }
}
});
}
tokens
}
struct ExportArgs {
use_wstp: bool,
exported_name: Option<Ident>,
hidden: bool,
}
fn parse_export_attribute_args(attrs: syn::AttributeArgs) -> Result<ExportArgs, Error> {
let mut use_wstp = false;
let mut hidden = false;
let mut exported_name: Option<Ident> = None;
for attr in attrs {
match attr {
NestedMeta::Meta(ref meta) => match meta {
Meta::Path(path) if path.is_ident("wstp") => {
if use_wstp {
return Err(Error::new(
attr.span(),
"duplicate export `wstp` attribute argument",
));
}
use_wstp = true;
},
Meta::Path(path) if path.is_ident("hidden") => {
if hidden {
return Err(Error::new(
attr.span(),
"duplicate export `hidden` attribute argument",
));
}
hidden = true;
},
Meta::List(_) | Meta::Path(_) => {
return Err(Error::new(
attr.span(),
"unrecognized export attribute argument",
));
},
Meta::NameValue(syn::MetaNameValue {
path,
eq_token: _,
lit,
}) => {
if path.is_ident("name") {
if exported_name.is_some() {
return Err(Error::new(
attr.span(),
"duplicate definition for `name`",
));
}
let lit_str = match lit {
syn::Lit::Str(str) => str,
_ => {
return Err(Error::new(
lit.span(),
"expected `name = \"...\"`",
))
},
};
exported_name = Some(
lit_str
.parse::<Ident>()
.map_err(|err| Error::new(lit_str.span(), err))?,
);
} else {
return Err(Error::new(
path.span(),
"unrecognized export attribute named argument",
));
}
},
},
NestedMeta::Lit(_) => {
return Err(Error::new(
attr.span(),
"unrecognized export attribute literal argument",
));
},
}
}
Ok(ExportArgs {
use_wstp,
exported_name,
hidden,
})
}