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