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
75 let func = syn::parse_macro_input!(item as syn::ItemFn);
77 let name = &func.sig.ident;
78 let vis = &func.vis;
79 let sig = &func.sig;
80 let block = &func.block;
81 let name_str = name.to_string();
82 let wrap_name = format_ident!("__yog_wrap_{}", name);
83 let init_name = format_ident!("__yog_export_init_{}", name);
84
85 let inputs = &func.sig.inputs;
87 let output = match &func.sig.output {
88 syn::ReturnType::Default => quote! { () },
89 syn::ReturnType::Type(_, ty) => quote! { #ty },
90 };
91
92 let call_fn = if inputs.is_empty() {
94 quote! { #name() }
95 } else {
96 let arg_names: Vec<_> = inputs.iter().map(|arg| {
98 match arg {
99 syn::FnArg::Typed(pat_type) => &pat_type.pat,
100 syn::FnArg::Receiver(_) => unreachable!(),
101 }
102 }).collect();
103 quote! { #name(#(#arg_names),*) }
104 };
105
106 let input_type = if inputs.is_empty() {
108 quote! { () }
109 } else {
110 match inputs.first().unwrap() {
112 syn::FnArg::Typed(pat_type) => {
113 let ty = &pat_type.ty;
114 quote! { #ty }
115 }
116 _ => quote! { () },
117 }
118 };
119
120 let expanded = quote! {
121 #vis #sig #block
122
123 #[doc(hidden)]
125 #[no_mangle]
126 pub unsafe extern "C" fn #wrap_name(
127 input_ptr: *const u8,
128 input_len: u32,
129 out_data: *mut *mut u8,
130 out_len: *mut u32,
131 out_cap: *mut u32,
132 ) {
133 let input_slice = std::slice::from_raw_parts(input_ptr, input_len as usize);
134 let args: #input_type = ::yog_api::rkyv::from_bytes::<_, ::yog_api::rkyv::rancor::Error>(input_slice)
135 .expect("yog: deserialization failed in export wrapper");
136 let result: #output = #call_fn;
137 let bytes: Vec<u8> = ::yog_api::rkyv::to_bytes::<_, 256>(&result)
138 .unwrap_or_default();
139 *out_data = bytes.as_ptr() as *mut u8;
140 *out_len = bytes.len() as u32;
141 *out_cap = bytes.capacity() as u32;
142 std::mem::forget(bytes); }
144
145 #[doc(hidden)]
147 #[allow(non_upper_case_globals)]
148 static #init_name: u8 = {
149 ::yog_api::__yog_export_registry()
150 .lock()
151 .unwrap()
152 .push((#name_str, #wrap_name as usize));
153 0
154 };
155 };
156
157 TokenStream::from(expanded)
158}
159
160#[proc_macro]
174pub fn import(input: TokenStream) -> TokenStream {
175 let import_block = syn::parse_macro_input!(input as ImportBlock);
176 let mod_name = &import_block.mod_name;
177 let mut out = proc_macro2::TokenStream::new();
178
179 for func in &import_block.funcs {
180 let name = &func.sig.ident;
181 let name_str = name.to_string();
182 let slot_name = format_ident!("__yog_slot_{}", name);
183 let bind_name = format_ident!("__yog_bind_{}", name);
184 let fn_sig = &func.sig;
185 let vis = &func.vis;
186 let inputs = &fn_sig.inputs;
187 let output = &fn_sig.output;
188
189 let _input_type = if inputs.is_empty() {
191 quote! { () }
192 } else {
193 match inputs.first().unwrap() {
194 syn::FnArg::Typed(pat_type) => {
195 let ty = &pat_type.ty;
196 quote! { #ty }
197 }
198 _ => quote! { () },
199 }
200 };
201
202 let output_type = match output {
203 syn::ReturnType::Default => quote! { () },
204 syn::ReturnType::Type(_, ty) => quote! { #ty },
205 };
206
207 let arg_names: Vec<_> = inputs.iter().map(|arg| {
209 match arg {
210 syn::FnArg::Typed(pat_type) => &pat_type.pat,
211 syn::FnArg::Receiver(_) => unreachable!("self not allowed in imports"),
212 }
213 }).collect();
214
215 let serialize_block = if inputs.is_empty() {
217 quote! { let input_bytes = Vec::new(); }
218 } else {
219 quote! {
220 let input_bytes = ::yog_api::rkyv::to_bytes::<_, 256>(&(#(#arg_names),*))
221 .unwrap_or_default();
222 }
223 };
224
225 let wrapper_ty = quote! {
227 unsafe extern "C" fn(
228 input_ptr: *const u8, input_len: u32,
229 out_data: *mut *mut u8, out_len: *mut u32, out_cap: *mut u32,
230 )
231 };
232
233 let wrapper = quote! {
234 #[allow(non_snake_case)]
235 #vis fn #name(#inputs) #output {
236 #serialize_block
237 let f = #slot_name.get()
238 .expect(concat!("yog: import '", #mod_name, ":", #name_str, "' not bound — is mod '", #mod_name, "' loaded?"));
239 let mut out_data: *mut u8 = std::ptr::null_mut();
240 let mut out_len: u32 = 0;
241 let mut out_cap: u32 = 0;
242 unsafe {
243 f(
244 input_bytes.as_ptr(), input_bytes.len() as u32,
245 &mut out_data, &mut out_len, &mut out_cap,
246 );
247 let output_slice = std::slice::from_raw_parts(out_data, out_len as usize);
248 let result: #output_type = ::yog_api::rkyv::from_bytes::<_, ::yog_api::rkyv::rancor::Error>(output_slice)
249 .expect(concat!("yog: deserialization failed in import '", #mod_name, ":", #name_str, "'"));
250 let _ = Vec::from_raw_parts(out_data, out_len as usize, out_cap as usize);
252 result
253 }
254 }
255
256 static #slot_name: ::std::sync::OnceLock<#wrapper_ty>
257 = ::std::sync::OnceLock::new();
258
259 #[doc(hidden)]
260 #[no_mangle]
261 pub unsafe extern "C" fn #bind_name(ptr: *const ::std::os::raw::c_void) {
262 let f: #wrapper_ty = ::std::mem::transmute(ptr);
263 #slot_name.set(f).ok();
264 }
265 };
266
267 out.extend(wrapper);
268 }
269
270 TokenStream::from(out)
271}
272
273struct ImportBlock {
276 mod_name: String,
277 funcs: Vec<syn::ItemFn>,
278}
279
280impl syn::parse::Parse for ImportBlock {
281 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
282 let from_kw: syn::Ident = input.parse()?;
283 if from_kw != "from" {
284 return Err(syn::Error::new(from_kw.span(), "expected `from`"));
285 }
286 let lit: syn::LitStr = input.parse()?;
287 let mod_name = lit.value();
288
289 let content;
290 syn::braced!(content in input);
291 let mut funcs = Vec::new();
292 while !content.is_empty() {
293 funcs.push(content.parse::<syn::ItemFn>()?);
294 let _ = content.parse::<syn::Token![;]>();
295 }
296
297 Ok(ImportBlock { mod_name, funcs })
298 }
299}