1use proc_macro::TokenStream;
33use quote::{format_ident, quote};
34
35#[proc_macro_attribute]
58pub fn yog_export(_attr: TokenStream, item: TokenStream) -> TokenStream {
59 if let Ok(s) = syn::parse2::<syn::ItemStruct>(proc_macro2::TokenStream::from(item.clone())) {
61 let vis = &s.vis;
62 let ident = &s.ident;
63 let generics = &s.generics;
64 let fields = &s.fields;
65 let attrs = &s.attrs;
66 return TokenStream::from(quote! {
67 #[derive(::yog_api::rkyv::Archive, ::yog_api::rkyv::Serialize, ::yog_api::rkyv::Deserialize)]
68 #(#attrs)*
69 #vis struct #ident #generics #fields
70 });
71 }
72 if let Ok(e) = syn::parse2::<syn::ItemEnum>(proc_macro2::TokenStream::from(item.clone())) {
73 let vis = &e.vis;
74 let ident = &e.ident;
75 let generics = &e.generics;
76 let variants = &e.variants;
77 let attrs = &e.attrs;
78 return TokenStream::from(quote! {
79 #[derive(::yog_api::rkyv::Archive, ::yog_api::rkyv::Serialize, ::yog_api::rkyv::Deserialize)]
80 #(#attrs)*
81 #vis enum #ident #generics { #variants }
82 });
83 }
84
85 let func = syn::parse_macro_input!(item as syn::ItemFn);
87 let name = &func.sig.ident;
88 let vis = &func.vis;
89 let sig = &func.sig;
90 let block = &func.block;
91 let name_str = name.to_string();
92 let wrap_name = format_ident!("__yog_wrap_{}", name);
93 let _init_name = format_ident!("__yog_export_init_{}", name);
94
95 let inputs = &func.sig.inputs;
97 let output = match &func.sig.output {
98 syn::ReturnType::Default => quote! { () },
99 syn::ReturnType::Type(_, ty) => quote! { #ty },
100 };
101
102 let call_fn = if inputs.is_empty() {
104 quote! { #name() }
105 } else {
106 let arg_names: Vec<_> = inputs.iter().map(|arg| {
108 match arg {
109 syn::FnArg::Typed(pat_type) => &pat_type.pat,
110 syn::FnArg::Receiver(_) => unreachable!(),
111 }
112 }).collect();
113 quote! { #name(#(#arg_names),*) }
114 };
115
116 let input_type = if inputs.is_empty() {
118 quote! { () }
119 } else {
120 match inputs.first().unwrap() {
122 syn::FnArg::Typed(pat_type) => {
123 let ty = &pat_type.ty;
124 quote! { #ty }
125 }
126 _ => quote! { () },
127 }
128 };
129
130 let expanded = quote! {
131 #vis #sig #block
132
133 #[doc(hidden)]
135 ctor::ctor! {
136 #[ctor(unsafe)]
137 static __yog_export_ctor_{name}: u8 = {
138 ::yog_api::__yog_export_registry()
139 .lock()
140 .unwrap()
141 .push(::yog_api::YogExportEntry {
142 name: #name_str,
143 ptr: #wrap_name as usize,
144 });
145 0
146 };
147 }
148
149 #[doc(hidden)]
151 #[no_mangle]
152 pub unsafe extern "C" fn #wrap_name(
153 input_ptr: *const u8,
154 input_len: u32,
155 out_data: *mut *mut u8,
156 out_len: *mut u32,
157 out_cap: *mut u32,
158 ) {
159 let input_slice = std::slice::from_raw_parts(input_ptr, input_len as usize);
160 let args: #input_type = ::yog_api::rkyv::from_bytes::<_, ::yog_api::rkyv::rancor::Error>(input_slice)
161 .expect("yog: deserialization failed in export wrapper");
162 let result: #output = #call_fn;
163 let aligned = ::yog_api::rkyv::to_bytes::<_, 256>(&result)
164 .unwrap_or_default();
165 let bytes: Vec<u8> = aligned.to_vec();
166 *out_data = bytes.as_ptr() as *mut u8;
167 *out_len = bytes.len() as u32;
168 *out_cap = bytes.capacity() as u32;
169 std::mem::forget(bytes); }
171 };
172
173 TokenStream::from(expanded)
174}
175
176#[proc_macro]
190pub fn import(input: TokenStream) -> TokenStream {
191 let import_block = syn::parse_macro_input!(input as ImportBlock);
192 let mod_name = &import_block.mod_name;
193 let mut out = proc_macro2::TokenStream::new();
194
195 for func in &import_block.funcs {
196 let name = &func.sig.ident;
197 let name_str = name.to_string();
198 let slot_name = format_ident!("__yog_slot_{}", name);
199 let bind_name = format_ident!("__yog_bind_{}", name);
200 let fn_sig = &func.sig;
201 let vis = &func.vis;
202 let inputs = &fn_sig.inputs;
203 let output = &fn_sig.output;
204
205 let _input_type = if inputs.is_empty() {
207 quote! { () }
208 } else {
209 match inputs.first().unwrap() {
210 syn::FnArg::Typed(pat_type) => {
211 let ty = &pat_type.ty;
212 quote! { #ty }
213 }
214 _ => quote! { () },
215 }
216 };
217
218 let output_type = match output {
219 syn::ReturnType::Default => quote! { () },
220 syn::ReturnType::Type(_, ty) => quote! { #ty },
221 };
222
223 let arg_names: Vec<_> = inputs.iter().map(|arg| {
225 match arg {
226 syn::FnArg::Typed(pat_type) => &pat_type.pat,
227 syn::FnArg::Receiver(_) => unreachable!("self not allowed in imports"),
228 }
229 }).collect();
230
231 let serialize_block = if inputs.is_empty() {
233 quote! { let input_bytes = Vec::new(); }
234 } else {
235 quote! {
236 let aligned = ::yog_api::rkyv::to_bytes::<_, 256>(&(#(#arg_names),*))
237 .unwrap_or_default();
238 let input_bytes: Vec<u8> = aligned.to_vec();
239 }
240 };
241
242 let wrapper_ty = quote! {
244 unsafe extern "C" fn(
245 input_ptr: *const u8, input_len: u32,
246 out_data: *mut *mut u8, out_len: *mut u32, out_cap: *mut u32,
247 )
248 };
249
250 let _init_name = format_ident!("__yog_import_init_{}", name);
251
252 let wrapper = quote! {
253 #[doc(hidden)]
255 #[ctor::ctor(unsafe)]
256 static __yog_import_ctor_{name}: u8 = {
257 ::yog_api::__yog_import_registry()
258 .lock()
259 .unwrap()
260 .push(::yog_api::YogImportEntry {
261 mod_id: #mod_name,
262 symbol: #name_str,
263 bind_fn: #bind_name as usize,
264 });
265 0
266 };
267
268 #[allow(non_snake_case)]
269 #vis fn #name(#inputs) #output {
270 #serialize_block
271 let f = #slot_name.get()
272 .expect(concat!("yog: import '", #mod_name, ":", #name_str, "' not bound — is mod '", #mod_name, "' loaded?"));
273 let mut out_data: *mut u8 = std::ptr::null_mut();
274 let mut out_len: u32 = 0;
275 let mut out_cap: u32 = 0;
276 unsafe {
277 f(
278 input_bytes.as_ptr(), input_bytes.len() as u32,
279 &mut out_data, &mut out_len, &mut out_cap,
280 );
281 let output_slice = std::slice::from_raw_parts(out_data, out_len as usize);
282 let result: #output_type = ::yog_api::rkyv::from_bytes::<_, ::yog_api::rkyv::rancor::Error>(output_slice)
283 .expect(concat!("yog: deserialization failed in import '", #mod_name, ":", #name_str, "'"));
284 let _ = Vec::from_raw_parts(out_data, out_len as usize, out_cap as usize);
286 result
287 }
288 }
289
290 static #slot_name: ::std::sync::OnceLock<#wrapper_ty>
291 = ::std::sync::OnceLock::new();
292
293 #[doc(hidden)]
294 #[no_mangle]
295 pub unsafe extern "C" fn #bind_name(ptr: *const ::std::os::raw::c_void) {
296 let f: #wrapper_ty = ::std::mem::transmute(ptr);
297 #slot_name.set(f).ok();
298 }
299 };
300
301 out.extend(wrapper);
302 }
303
304 TokenStream::from(out)
305}
306
307struct ImportBlock {
310 mod_name: String,
311 funcs: Vec<syn::ItemFn>,
312}
313
314impl syn::parse::Parse for ImportBlock {
315 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
316 let from_kw: syn::Ident = input.parse()?;
317 if from_kw != "from" {
318 return Err(syn::Error::new(from_kw.span(), "expected `from`"));
319 }
320 let lit: syn::LitStr = input.parse()?;
321 let mod_name = lit.value();
322
323 let content;
324 syn::braced!(content in input);
325 let mut funcs = Vec::new();
326 while !content.is_empty() {
327 funcs.push(content.parse::<syn::ItemFn>()?);
328 let _ = content.parse::<syn::Token![;]>();
329 }
330
331 Ok(ImportBlock { mod_name, funcs })
332 }
333}