1use proc_macro::TokenStream;
11use quote::{format_ident, quote};
12
13#[proc_macro_attribute]
20pub fn yog_export(_attr: TokenStream, item: TokenStream) -> TokenStream {
21 if let Ok(s) = syn::parse2::<syn::ItemStruct>(proc_macro2::TokenStream::from(item.clone())) {
23 let vis = &s.vis;
24 let ident = &s.ident;
25 let generics = &s.generics;
26 let fields = &s.fields;
27 let attrs = &s.attrs;
28 return TokenStream::from(quote! {
29 #[derive(::yog_api::rkyv::Archive, ::yog_api::rkyv::Serialize, ::yog_api::rkyv::Deserialize)]
30 #(#attrs)*
31 #vis struct #ident #generics #fields
32 });
33 }
34 if let Ok(e) = syn::parse2::<syn::ItemEnum>(proc_macro2::TokenStream::from(item.clone())) {
35 let vis = &e.vis;
36 let ident = &e.ident;
37 let generics = &e.generics;
38 let variants = &e.variants;
39 let attrs = &e.attrs;
40 return TokenStream::from(quote! {
41 #[derive(::yog_api::rkyv::Archive, ::yog_api::rkyv::Serialize, ::yog_api::rkyv::Deserialize)]
42 #(#attrs)*
43 #vis enum #ident #generics { #variants }
44 });
45 }
46
47 let func = syn::parse_macro_input!(item as syn::ItemFn);
48 let name = &func.sig.ident;
49 let vis = &func.vis;
50 let sig = &func.sig;
51 let block = &func.block;
52 let name_str = name.to_string();
53 let wrap_name = format_ident!("__yog_wrap_{}", name);
54 let ctor_name = format_ident!("__yog_ctor_register_{}", name);
55
56 let inputs = &func.sig.inputs;
57 let output = match &func.sig.output {
58 syn::ReturnType::Default => quote! { () },
59 syn::ReturnType::Type(_, ty) => quote! { #ty },
60 };
61
62 let call_fn = if inputs.is_empty() {
67 quote! { #name() }
68 } else {
69 quote! { #name(args) }
70 };
71
72 let input_type = if inputs.is_empty() {
73 quote! { () }
74 } else {
75 match inputs.first().unwrap() {
76 syn::FnArg::Typed(pat_type) => { let ty = &pat_type.ty; quote! { #ty } }
77 _ => quote! { () },
78 }
79 };
80
81 let expanded = quote! {
82 #vis #sig #block
83
84 #[doc(hidden)]
85 #[no_mangle]
86 pub unsafe extern "C" fn #wrap_name(
87 input_ptr: *const u8, input_len: u32,
88 out_data: *mut *mut u8, out_len: *mut u32, out_cap: *mut u32,
89 ) {
90 let input_slice = std::slice::from_raw_parts(input_ptr, input_len as usize);
91 let args: #input_type = ::yog_api::rkyv::from_bytes::<_, ::yog_api::rkyv::rancor::Error>(input_slice)
92 .expect("yog: deser failed");
93 let result: #output = #call_fn;
94 let aligned = ::yog_api::rkyv::to_bytes::<::yog_api::rkyv::rancor::Error>(&result).unwrap_or_default();
95 let bytes: Vec<u8> = aligned.to_vec();
96 *out_data = bytes.as_ptr() as *mut u8;
97 *out_len = bytes.len() as u32;
98 *out_cap = bytes.capacity() as u32;
99 std::mem::forget(bytes);
100 }
101
102 #[::yog_api::ctor::ctor(unsafe, crate_path = ::yog_api::ctor)]
106 #[doc(hidden)]
107 fn #ctor_name() {
108 ::yog_api::__yog_export_registry().lock().unwrap().push(
109 ::yog_api::YogExportEntry { name: #name_str, ptr: #wrap_name as usize }
110 );
111 }
112 };
113
114 TokenStream::from(expanded)
115}
116
117#[proc_macro]
120pub fn import(input: TokenStream) -> TokenStream {
121 let import_block = syn::parse_macro_input!(input as ImportBlock);
122 let mod_name = &import_block.mod_name;
123 let mod_ident = format_ident!("{}", mod_name.replace('-', "_"));
124 let mut items = proc_macro2::TokenStream::new();
125
126 for item in &import_block.items {
127 match item {
128 ImportItem::Func(func) => { items.extend(generate_import_fn(func, mod_name)); }
129 ImportItem::Struct(s) => {
130 let vis = &s.vis; let ident = &s.ident; let generics = &s.generics;
131 let fields = &s.fields; let attrs = &s.attrs;
132 items.extend(quote! {
133 #[derive(::yog_api::rkyv::Archive, ::yog_api::rkyv::Serialize, ::yog_api::rkyv::Deserialize)]
134 #(#attrs)* #vis struct #ident #generics #fields
135 });
136 }
137 }
138 }
139
140 let out = quote! { pub mod #mod_ident { #items } };
141 TokenStream::from(out)
142}
143
144fn generate_import_fn(func: &syn::ItemFn, mod_name: &str) -> proc_macro2::TokenStream {
145 let name = &func.sig.ident;
146 let name_str = name.to_string();
147 let slot_name = format_ident!("__YOG_SLOT_{}", name_str.to_uppercase());
150 let bind_name = format_ident!("__yog_bind_{}", name);
151 let fn_sig = &func.sig;
152 let vis = &func.vis;
153 let inputs = &fn_sig.inputs;
154 let output = &fn_sig.output;
155
156 let output_type = match output {
157 syn::ReturnType::Default => quote! { () },
158 syn::ReturnType::Type(_, ty) => quote! { #ty },
159 };
160
161 let arg_names: Vec<_> = inputs.iter().map(|arg| {
162 match arg { syn::FnArg::Typed(pat_type) => &pat_type.pat, _ => unreachable!() }
163 }).collect();
164
165 let serialize_block = if inputs.is_empty() {
166 quote! { let input_bytes = Vec::new(); }
167 } else {
168 quote! {
169 let aligned = ::yog_api::rkyv::to_bytes::<::yog_api::rkyv::rancor::Error>(&(#(#arg_names),*)).unwrap_or_default();
170 let input_bytes: Vec<u8> = aligned.to_vec();
171 }
172 };
173
174 let wrapper_ty = quote! {
175 unsafe extern "C" fn(input_ptr: *const u8, input_len: u32,
176 out_data: *mut *mut u8, out_len: *mut u32, out_cap: *mut u32)
177 };
178
179 quote! {
180 #[allow(non_snake_case)]
181 #vis fn #name(#inputs) #output {
182 #serialize_block
183 let f = #slot_name.get()
184 .expect(concat!("yog: import '", #mod_name, ":", #name_str, "' not bound"));
185 let mut out_data: *mut u8 = std::ptr::null_mut();
186 let mut out_len: u32 = 0; let mut out_cap: u32 = 0;
187 unsafe {
188 f(input_bytes.as_ptr(), input_bytes.len() as u32,
189 &mut out_data, &mut out_len, &mut out_cap);
190 let output_slice = std::slice::from_raw_parts(out_data, out_len as usize);
191 let result: #output_type = ::yog_api::rkyv::from_bytes::<_, ::yog_api::rkyv::rancor::Error>(output_slice)
192 .expect(concat!("yog: deser failed in import '", #mod_name, ":", #name_str, "'"));
193 let _ = Vec::from_raw_parts(out_data, out_len as usize, out_cap as usize);
194 result
195 }
196 }
197
198 static #slot_name: ::std::sync::OnceLock<#wrapper_ty> = ::std::sync::OnceLock::new();
199
200 #[doc(hidden)] #[no_mangle]
201 pub unsafe extern "C" fn #bind_name(ptr: *const ::std::os::raw::c_void) {
202 let f: #wrapper_ty = ::std::mem::transmute(ptr);
203 #slot_name.set(f).ok();
204 }
205 }
206}
207
208struct ImportBlock { mod_name: String, items: Vec<ImportItem> }
211enum ImportItem { Func(syn::ItemFn), Struct(syn::ItemStruct) }
212
213impl syn::parse::Parse for ImportBlock {
214 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
215 let from_kw: syn::Ident = input.parse()?;
216 if from_kw != "from" { return Err(syn::Error::new(from_kw.span(), "expected `from`")); }
217 let lit: syn::LitStr = input.parse()?;
218 let mod_name = lit.value();
219 let content; syn::braced!(content in input);
220 let mut items = Vec::new();
221 while !content.is_empty() {
222 if let Ok(f) = content.parse::<syn::ItemFn>() { items.push(ImportItem::Func(f)); let _ = content.parse::<syn::Token![;]>(); }
223 else if let Ok(s) = content.parse::<syn::ItemStruct>() { items.push(ImportItem::Struct(s)); let _ = content.parse::<syn::Token![;]>(); }
224 else { break; }
225 }
226 Ok(ImportBlock { mod_name, items })
227 }
228}