use proc_macro::TokenStream;
use quote::{format_ident, quote};
#[proc_macro_attribute]
pub fn yog_export(_attr: TokenStream, item: TokenStream) -> TokenStream {
let func = syn::parse_macro_input!(item as syn::ItemFn);
let name = &func.sig.ident;
let vis = &func.vis;
let sig = &func.sig;
let block = &func.block;
let name_str = name.to_string();
let getter_name = format_ident!("__yog_export_get_{}", name);
let expanded = quote! {
#vis #sig #block
#[doc(hidden)]
#[no_mangle]
pub unsafe extern "C" fn #getter_name(
out_name: *mut *const ::std::os::raw::c_char,
out_ptr: *mut *const ::std::os::raw::c_void,
) {
if !out_name.is_null() {
*out_name = concat!(#name_str, "\0").as_ptr() as *const ::std::os::raw::c_char;
}
if !out_ptr.is_null() {
*out_ptr = #name as *const ::std::os::raw::c_void;
}
}
};
TokenStream::from(expanded)
}
#[proc_macro]
pub fn import(input: TokenStream) -> TokenStream {
let import_block = syn::parse_macro_input!(input as ImportBlock);
let mod_name = &import_block.mod_name;
let mut out = proc_macro2::TokenStream::new();
for func in &import_block.funcs {
let name = &func.sig.ident;
let name_str = name.to_string();
let slot_name = format_ident!("__yog_slot_{}", name);
let bind_name = format_ident!("__yog_bind_{}", name);
let fn_sig = &func.sig;
let vis = &func.vis;
let inputs = &fn_sig.inputs;
let output = &fn_sig.output;
let arg_names: Vec<_> = inputs.iter().map(|arg| {
match arg {
syn::FnArg::Typed(pat_type) => &pat_type.pat,
syn::FnArg::Receiver(_) => unreachable!("self not allowed in imports"),
}
}).collect();
let wrapper = quote! {
#[allow(non_snake_case)]
#vis fn #name(#inputs) #output {
let f = #slot_name.get()
.expect(concat!("yog: import '", #mod_name, ":", #name_str, "' not bound — is mod '", #mod_name, "' loaded?"));
unsafe { f(#(#arg_names),*) }
}
static #slot_name: ::std::sync::OnceLock<unsafe extern "C" fn(#inputs) #output>
= ::std::sync::OnceLock::new();
#[doc(hidden)]
#[no_mangle]
pub unsafe extern "C" fn #bind_name(ptr: *const ::std::os::raw::c_void) {
let f: unsafe extern "C" fn(#inputs) #output = ::std::mem::transmute(ptr);
#slot_name.set(f).ok();
}
};
out.extend(wrapper);
}
TokenStream::from(out)
}
struct ImportBlock {
mod_name: String,
funcs: Vec<syn::ItemFn>,
}
impl syn::parse::Parse for ImportBlock {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let from_kw: syn::Ident = input.parse()?;
if from_kw != "from" {
return Err(syn::Error::new(from_kw.span(), "expected `from`"));
}
let lit: syn::LitStr = input.parse()?;
let mod_name = lit.value();
let content;
syn::braced!(content in input);
let mut funcs = Vec::new();
while !content.is_empty() {
funcs.push(content.parse::<syn::ItemFn>()?);
let _ = content.parse::<syn::Token![;]>();
}
Ok(ImportBlock { mod_name, funcs })
}
}