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