Skip to main content

yog_interop/
lib.rs

1//! Proc-macros for Yog inter-mod communication.
2//!
3//! `#[yog_export]` on fn: generates C-ABI wrapper. Register manually:
4//! ```ignore
5//! registry.interop().export("fname", __yog_wrap_fname as *const c_void);
6//! ```
7//!
8//! `import!` macro: generates Rust wrapper + binding slot.
9
10use proc_macro::TokenStream;
11use quote::{format_ident, quote};
12
13// ── #[yog_export] ─────────────────────────────────────────────────────────
14
15/// Marks a function for inter-mod export. Generates a C-ABI wrapper.
16/// Auto-registration TBD (ctor/linkme debugging in progress).
17/// For now, register manually in `Mod::register()`:
18/// ```ignore
19/// registry.interop().export("fname", __yog_wrap_fname as *const c_void);
20/// ```
21#[proc_macro_attribute]
22pub fn yog_export(_attr: TokenStream, item: TokenStream) -> TokenStream {
23    // Try parsing as a struct or enum first (auto rkyv derives)
24    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// ── import! macro ────────────────────────────────────────────────────────
123
124#[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
225// ── Parse helpers ────────────────────────────────────────────────────────
226
227struct 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}