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