1use proc_macro::TokenStream;
33use quote::{format_ident, quote};
34
35#[proc_macro_attribute]
43pub fn yog_export(_attr: TokenStream, item: TokenStream) -> TokenStream {
44 let func = syn::parse_macro_input!(item as syn::ItemFn);
45 let name = &func.sig.ident;
46 let vis = &func.vis;
47 let sig = &func.sig;
48 let block = &func.block;
49 let name_str = name.to_string();
50 let getter_name = format_ident!("__yog_export_get_{}", name);
51
52 let expanded = quote! {
53 #vis #sig #block
54
55 #[doc(hidden)]
56 #[no_mangle]
57 pub unsafe extern "C" fn #getter_name(
58 out_name: *mut *const ::std::os::raw::c_char,
59 out_ptr: *mut *const ::std::os::raw::c_void,
60 ) {
61 if !out_name.is_null() {
62 *out_name = concat!(#name_str, "\0").as_ptr() as *const ::std::os::raw::c_char;
63 }
64 if !out_ptr.is_null() {
65 *out_ptr = #name as *const ::std::os::raw::c_void;
66 }
67 }
68 };
69
70 TokenStream::from(expanded)
71}
72
73#[proc_macro]
87pub fn import(input: TokenStream) -> TokenStream {
88 let import_block = syn::parse_macro_input!(input as ImportBlock);
89 let mod_name = &import_block.mod_name;
90 let mut out = proc_macro2::TokenStream::new();
91
92 for func in &import_block.funcs {
93 let name = &func.sig.ident;
94 let name_str = name.to_string();
95 let slot_name = format_ident!("__yog_slot_{}", name);
96 let bind_name = format_ident!("__yog_bind_{}", name);
97 let fn_sig = &func.sig;
98 let vis = &func.vis;
99 let inputs = &fn_sig.inputs;
100 let output = &fn_sig.output;
101
102 let arg_names: Vec<_> = inputs.iter().map(|arg| {
103 match arg {
104 syn::FnArg::Typed(pat_type) => &pat_type.pat,
105 syn::FnArg::Receiver(_) => unreachable!("self not allowed in imports"),
106 }
107 }).collect();
108
109 let wrapper = quote! {
110 #[allow(non_snake_case)]
111 #vis fn #name(#inputs) #output {
112 let f = #slot_name.get()
113 .expect(concat!("yog: import '", #mod_name, ":", #name_str, "' not bound — is mod '", #mod_name, "' loaded?"));
114 unsafe { f(#(#arg_names),*) }
115 }
116
117 static #slot_name: ::std::sync::OnceLock<unsafe extern "C" fn(#inputs) #output>
118 = ::std::sync::OnceLock::new();
119
120 #[doc(hidden)]
121 #[no_mangle]
122 pub unsafe extern "C" fn #bind_name(ptr: *const ::std::os::raw::c_void) {
123 let f: unsafe extern "C" fn(#inputs) #output = ::std::mem::transmute(ptr);
124 #slot_name.set(f).ok();
125 }
126 };
127
128 out.extend(wrapper);
129 }
130
131 TokenStream::from(out)
132}
133
134struct ImportBlock {
137 mod_name: String,
138 funcs: Vec<syn::ItemFn>,
139}
140
141impl syn::parse::Parse for ImportBlock {
142 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
143 let from_kw: syn::Ident = input.parse()?;
144 if from_kw != "from" {
145 return Err(syn::Error::new(from_kw.span(), "expected `from`"));
146 }
147 let lit: syn::LitStr = input.parse()?;
148 let mod_name = lit.value();
149
150 let content;
151 syn::braced!(content in input);
152 let mut funcs = Vec::new();
153 while !content.is_empty() {
154 funcs.push(content.parse::<syn::ItemFn>()?);
155 let _ = content.parse::<syn::Token![;]>();
156 }
157
158 Ok(ImportBlock { mod_name, funcs })
159 }
160}