use proc_macro::TokenStream;
use quote::{format_ident, quote};
#[proc_macro_attribute]
pub fn yog_export(_attr: TokenStream, item: TokenStream) -> TokenStream {
if let Ok(s) = syn::parse2::<syn::ItemStruct>(proc_macro2::TokenStream::from(item.clone())) {
let vis = &s.vis;
let ident = &s.ident;
let generics = &s.generics;
let fields = &s.fields;
let attrs = &s.attrs;
return TokenStream::from(quote! {
#[derive(::yog_api::rkyv::Archive, ::yog_api::rkyv::Serialize, ::yog_api::rkyv::Deserialize)]
#(#attrs)*
#vis struct #ident #generics #fields
});
}
if let Ok(e) = syn::parse2::<syn::ItemEnum>(proc_macro2::TokenStream::from(item.clone())) {
let vis = &e.vis;
let ident = &e.ident;
let generics = &e.generics;
let variants = &e.variants;
let attrs = &e.attrs;
return TokenStream::from(quote! {
#[derive(::yog_api::rkyv::Archive, ::yog_api::rkyv::Serialize, ::yog_api::rkyv::Deserialize)]
#(#attrs)*
#vis enum #ident #generics { #variants }
});
}
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 wrap_name = format_ident!("__yog_wrap_{}", name);
let _init_name = format_ident!("__yog_export_init_{}", name);
let inputs = &func.sig.inputs;
let output = match &func.sig.output {
syn::ReturnType::Default => quote! { () },
syn::ReturnType::Type(_, ty) => quote! { #ty },
};
let call_fn = if inputs.is_empty() {
quote! { #name() }
} else {
let arg_names: Vec<_> = inputs.iter().map(|arg| {
match arg {
syn::FnArg::Typed(pat_type) => &pat_type.pat,
syn::FnArg::Receiver(_) => unreachable!(),
}
}).collect();
quote! { #name(#(#arg_names),*) }
};
let input_type = if inputs.is_empty() {
quote! { () }
} else {
match inputs.first().unwrap() {
syn::FnArg::Typed(pat_type) => {
let ty = &pat_type.ty;
quote! { #ty }
}
_ => quote! { () },
}
};
let expanded = quote! {
#vis #sig #block
#[doc(hidden)]
ctor::ctor! {
#[ctor(unsafe)]
static __yog_export_ctor_{name}: u8 = {
::yog_api::__yog_export_registry()
.lock()
.unwrap()
.push(::yog_api::YogExportEntry {
name: #name_str,
ptr: #wrap_name as usize,
});
0
};
}
#[doc(hidden)]
#[no_mangle]
pub unsafe extern "C" fn #wrap_name(
input_ptr: *const u8,
input_len: u32,
out_data: *mut *mut u8,
out_len: *mut u32,
out_cap: *mut u32,
) {
let input_slice = std::slice::from_raw_parts(input_ptr, input_len as usize);
let args: #input_type = ::yog_api::rkyv::from_bytes::<_, ::yog_api::rkyv::rancor::Error>(input_slice)
.expect("yog: deserialization failed in export wrapper");
let result: #output = #call_fn;
let aligned = ::yog_api::rkyv::to_bytes::<_, 256>(&result)
.unwrap_or_default();
let bytes: Vec<u8> = aligned.to_vec();
*out_data = bytes.as_ptr() as *mut u8;
*out_len = bytes.len() as u32;
*out_cap = bytes.capacity() as u32;
std::mem::forget(bytes); }
};
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 _input_type = if inputs.is_empty() {
quote! { () }
} else {
match inputs.first().unwrap() {
syn::FnArg::Typed(pat_type) => {
let ty = &pat_type.ty;
quote! { #ty }
}
_ => quote! { () },
}
};
let output_type = match output {
syn::ReturnType::Default => quote! { () },
syn::ReturnType::Type(_, ty) => quote! { #ty },
};
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 serialize_block = if inputs.is_empty() {
quote! { let input_bytes = Vec::new(); }
} else {
quote! {
let aligned = ::yog_api::rkyv::to_bytes::<_, 256>(&(#(#arg_names),*))
.unwrap_or_default();
let input_bytes: Vec<u8> = aligned.to_vec();
}
};
let wrapper_ty = quote! {
unsafe extern "C" fn(
input_ptr: *const u8, input_len: u32,
out_data: *mut *mut u8, out_len: *mut u32, out_cap: *mut u32,
)
};
let _init_name = format_ident!("__yog_import_init_{}", name);
let wrapper = quote! {
#[doc(hidden)]
ctor::ctor! {
#[ctor(unsafe)]
static __yog_import_ctor_{name}: u8 = {
::yog_api::__yog_import_registry()
.lock()
.unwrap()
.push(::yog_api::YogImportEntry {
mod_id: #mod_name,
symbol: #name_str,
bind_fn: #bind_name as usize,
});
0
};
}
#[allow(non_snake_case)]
#vis fn #name(#inputs) #output {
#serialize_block
let f = #slot_name.get()
.expect(concat!("yog: import '", #mod_name, ":", #name_str, "' not bound — is mod '", #mod_name, "' loaded?"));
let mut out_data: *mut u8 = std::ptr::null_mut();
let mut out_len: u32 = 0;
let mut out_cap: u32 = 0;
unsafe {
f(
input_bytes.as_ptr(), input_bytes.len() as u32,
&mut out_data, &mut out_len, &mut out_cap,
);
let output_slice = std::slice::from_raw_parts(out_data, out_len as usize);
let result: #output_type = ::yog_api::rkyv::from_bytes::<_, ::yog_api::rkyv::rancor::Error>(output_slice)
.expect(concat!("yog: deserialization failed in import '", #mod_name, ":", #name_str, "'"));
let _ = Vec::from_raw_parts(out_data, out_len as usize, out_cap as usize);
result
}
}
static #slot_name: ::std::sync::OnceLock<#wrapper_ty>
= ::std::sync::OnceLock::new();
#[doc(hidden)]
#[no_mangle]
pub unsafe extern "C" fn #bind_name(ptr: *const ::std::os::raw::c_void) {
let f: #wrapper_ty = ::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 })
}
}