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 #[doc(hidden)]
88 #[no_mangle]
89 pub unsafe extern "C" fn #wrap_name(
90 input_ptr: *const u8, input_len: u32,
91 out_data: *mut *mut u8, out_len: *mut u32, out_cap: *mut u32,
92 ) {
93 let input_slice = std::slice::from_raw_parts(input_ptr, input_len as usize);
94 let args: #input_type = ::yog_api::rkyv::from_bytes::<_, ::yog_api::rkyv::rancor::Error>(input_slice)
95 .expect("yog: deser failed");
96 let result: #output = #call_fn;
97 let aligned = ::yog_api::rkyv::to_bytes::<::yog_api::rkyv::rancor::Error>(&result).unwrap_or_default();
98 let bytes: Vec<u8> = aligned.to_vec();
99 *out_data = bytes.as_ptr() as *mut u8;
100 *out_len = bytes.len() as u32;
101 *out_cap = bytes.capacity() as u32;
102 std::mem::forget(bytes);
103 }
104 };
105
106 TokenStream::from(expanded)
107}
108
109#[proc_macro]
112pub fn import(input: TokenStream) -> TokenStream {
113 let import_block = syn::parse_macro_input!(input as ImportBlock);
114 let mod_name = &import_block.mod_name;
115 let mod_ident = format_ident!("{}", mod_name.replace('-', "_"));
116 let mut items = proc_macro2::TokenStream::new();
117
118 for item in &import_block.items {
119 match item {
120 ImportItem::Func(func) => { items.extend(generate_import_fn(func, mod_name)); }
121 ImportItem::Struct(s) => {
122 let vis = &s.vis; let ident = &s.ident; let generics = &s.generics;
123 let fields = &s.fields; let attrs = &s.attrs;
124 items.extend(quote! {
125 #[derive(::yog_api::rkyv::Archive, ::yog_api::rkyv::Serialize, ::yog_api::rkyv::Deserialize)]
126 #(#attrs)* #vis struct #ident #generics #fields
127 });
128 }
129 }
130 }
131
132 let out = quote! { pub mod #mod_ident { #items } };
133 TokenStream::from(out)
134}
135
136fn generate_import_fn(func: &syn::ItemFn, mod_name: &str) -> proc_macro2::TokenStream {
137 let name = &func.sig.ident;
138 let name_str = name.to_string();
139 let slot_name = format_ident!("__yog_slot_{}", name);
140 let bind_name = format_ident!("__yog_bind_{}", name);
141 let fn_sig = &func.sig;
142 let vis = &func.vis;
143 let inputs = &fn_sig.inputs;
144 let output = &fn_sig.output;
145
146 let output_type = match output {
147 syn::ReturnType::Default => quote! { () },
148 syn::ReturnType::Type(_, ty) => quote! { #ty },
149 };
150
151 let arg_names: Vec<_> = inputs.iter().map(|arg| {
152 match arg { syn::FnArg::Typed(pat_type) => &pat_type.pat, _ => unreachable!() }
153 }).collect();
154
155 let serialize_block = if inputs.is_empty() {
156 quote! { let input_bytes = Vec::new(); }
157 } else {
158 quote! {
159 let aligned = ::yog_api::rkyv::to_bytes::<::yog_api::rkyv::rancor::Error>(&(#(#arg_names),*)).unwrap_or_default();
160 let input_bytes: Vec<u8> = aligned.to_vec();
161 }
162 };
163
164 let wrapper_ty = quote! {
165 unsafe extern "C" fn(input_ptr: *const u8, input_len: u32,
166 out_data: *mut *mut u8, out_len: *mut u32, out_cap: *mut u32)
167 };
168
169 quote! {
170 #[allow(non_snake_case)]
171 #vis fn #name(#inputs) #output {
172 #serialize_block
173 let f = #slot_name.get()
174 .expect(concat!("yog: import '", #mod_name, ":", #name_str, "' not bound"));
175 let mut out_data: *mut u8 = std::ptr::null_mut();
176 let mut out_len: u32 = 0; let mut out_cap: u32 = 0;
177 unsafe {
178 f(input_bytes.as_ptr(), input_bytes.len() as u32,
179 &mut out_data, &mut out_len, &mut out_cap);
180 let output_slice = std::slice::from_raw_parts(out_data, out_len as usize);
181 let result: #output_type = ::yog_api::rkyv::from_bytes::<_, ::yog_api::rkyv::rancor::Error>(output_slice)
182 .expect(concat!("yog: deser failed in import '", #mod_name, ":", #name_str, "'"));
183 let _ = Vec::from_raw_parts(out_data, out_len as usize, out_cap as usize);
184 result
185 }
186 }
187
188 static #slot_name: ::std::sync::OnceLock<#wrapper_ty> = ::std::sync::OnceLock::new();
189
190 #[doc(hidden)] #[no_mangle]
191 pub unsafe extern "C" fn #bind_name(ptr: *const ::std::os::raw::c_void) {
192 let f: #wrapper_ty = ::std::mem::transmute(ptr);
193 #slot_name.set(f).ok();
194 }
195 }
196}
197
198struct ImportBlock { mod_name: String, items: Vec<ImportItem> }
201enum ImportItem { Func(syn::ItemFn), Struct(syn::ItemStruct) }
202
203impl syn::parse::Parse for ImportBlock {
204 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
205 let from_kw: syn::Ident = input.parse()?;
206 if from_kw != "from" { return Err(syn::Error::new(from_kw.span(), "expected `from`")); }
207 let lit: syn::LitStr = input.parse()?;
208 let mod_name = lit.value();
209 let content; syn::braced!(content in input);
210 let mut items = Vec::new();
211 while !content.is_empty() {
212 if let Ok(f) = content.parse::<syn::ItemFn>() { items.push(ImportItem::Func(f)); let _ = content.parse::<syn::Token![;]>(); }
213 else if let Ok(s) = content.parse::<syn::ItemStruct>() { items.push(ImportItem::Struct(s)); let _ = content.parse::<syn::Token![;]>(); }
214 else { break; }
215 }
216 Ok(ImportBlock { mod_name, items })
217 }
218}