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