1use proc_macro::TokenStream;
9use quote::{format_ident, quote};
10
11mod mirror;
12
13fn schema_derive() -> proc_macro2::TokenStream {
18 quote! {
19 #[derive(::yog_api::yog_data::Schema)]
20 #[data(crate = "::yog_api::yog_data")]
21 }
22}
23
24fn enum_tag(e: &syn::ItemEnum) -> proc_macro2::TokenStream {
27 use yog_ids::data_attr::{DATA, TAG};
28 let has_data = e
29 .variants
30 .iter()
31 .any(|v| !matches!(v.fields, syn::Fields::Unit));
32 let tagged = e.attrs.iter().filter(|a| a.path().is_ident(DATA)).any(|a| {
33 let mut tag = false;
34 let _ = a.parse_nested_meta(|m| {
36 tag |= m.path.is_ident(TAG);
37 if m.input.peek(syn::Token![=]) {
38 m.value()?.parse::<syn::Expr>()?;
39 }
40 Ok(())
41 });
42 tag
43 });
44 if !has_data || tagged {
45 return quote! {};
46 }
47 let tag = yog_ids::word::TYPE;
48 let (data, tag_key) = (format_ident!("{DATA}"), format_ident!("{TAG}"));
49 quote! { #[#data(#tag_key = #tag)] }
50}
51
52fn is_registry_ty(ty: &syn::Type) -> bool {
55 let ty = match ty {
56 syn::Type::Reference(r) => &*r.elem,
57 other => other,
58 };
59 quote! { #ty }
60 .to_string()
61 .replace(' ', "")
62 .ends_with("Registry")
63}
64
65fn call_params(sig: &syn::Signature) -> (bool, Vec<(syn::Ident, syn::Type)>) {
68 let mut typed: Vec<&syn::PatType> = sig
69 .inputs
70 .iter()
71 .filter_map(|a| match a {
72 syn::FnArg::Typed(pt) => Some(pt),
73 syn::FnArg::Receiver(_) => None,
74 })
75 .collect();
76 let registry = typed.first().is_some_and(|pt| is_registry_ty(&pt.ty));
77 if registry {
78 typed.remove(0);
79 }
80 let params = typed
81 .iter()
82 .enumerate()
83 .map(|(i, pt)| {
84 let name = match &*pt.pat {
85 syn::Pat::Ident(p) => p.ident.clone(),
86 _ => format_ident!("__yog_arg{}", i),
87 };
88 (name, (*pt.ty).clone())
89 })
90 .collect();
91 (registry, params)
92}
93
94struct Args {
98 ty: proc_macro2::TokenStream,
100 def: proc_macro2::TokenStream,
102 pack: proc_macro2::TokenStream,
104 unpack: proc_macro2::TokenStream,
106}
107
108fn args_of(name: &syn::Ident, params: &[(syn::Ident, syn::Type)]) -> Args {
109 if let [(param, ty)] = params {
110 return Args {
111 ty: quote! { #ty },
112 def: quote! {},
113 pack: quote! { &#param },
114 unpack: quote! { a },
115 };
116 }
117 let args = format_ident!("__YogArgs_{}", name);
118 let fields = params.iter().map(|(n, t)| quote! { #n: #t });
119 let names: Vec<&syn::Ident> = params.iter().map(|(n, _)| n).collect();
120 let derive = schema_derive();
121 Args {
122 ty: quote! { #args },
123 def: quote! {
124 #[doc(hidden)]
125 #[allow(non_camel_case_types)]
127 #derive
128 struct #args { #(#fields),* }
129 },
130 pack: quote! { &#args { #(#names),* } },
131 unpack: quote! { #(a.#names),* },
132 }
133}
134
135fn output_type(sig: &syn::Signature) -> proc_macro2::TokenStream {
136 match &sig.output {
137 syn::ReturnType::Default => quote! { () },
138 syn::ReturnType::Type(_, ty) => quote! { #ty },
139 }
140}
141
142fn doc_of(attrs: &[syn::Attribute]) -> String {
146 let lines: Vec<String> = attrs
147 .iter()
148 .filter(|a| a.path().is_ident("doc"))
149 .filter_map(|a| match &a.meta {
150 syn::Meta::NameValue(nv) => match &nv.value {
151 syn::Expr::Lit(syn::ExprLit {
152 lit: syn::Lit::Str(s),
153 ..
154 }) => Some(s.value().trim().to_owned()),
155 _ => None,
156 },
157 _ => None,
158 })
159 .collect();
160 lines.join(" ")
161}
162
163fn implements_of(attr: TokenStream) -> syn::Result<Vec<String>> {
165 let mut out = Vec::new();
166 let parser = syn::meta::parser(|meta| {
167 if meta.path.is_ident("implements") {
168 out.push(meta.value()?.parse::<syn::LitStr>()?.value());
169 Ok(())
170 } else {
171 Err(meta.error("unknown `yog_export` argument (implements)"))
172 }
173 });
174 syn::parse::Parser::parse(parser, attr)?;
175 Ok(out)
176}
177
178#[proc_macro_attribute]
179pub fn yog_export(attr: TokenStream, item: TokenStream) -> TokenStream {
180 let implements = match implements_of(attr) {
181 Ok(i) => i,
182 Err(e) => return e.to_compile_error().into(),
183 };
184 let tokens = proc_macro2::TokenStream::from(item.clone());
185 if syn::parse2::<syn::ItemStruct>(tokens.clone()).is_ok() {
186 let derive = schema_derive();
187 return TokenStream::from(quote! { #derive #tokens });
188 }
189 if let Ok(e) = syn::parse2::<syn::ItemEnum>(tokens.clone()) {
190 let derive = schema_derive();
191 let tag = enum_tag(&e);
192 return TokenStream::from(quote! { #derive #tag #tokens });
193 }
194 let func = syn::parse_macro_input!(item as syn::ItemFn);
195 let name = &func.sig.ident;
196 let name_str = name.to_string();
197 let serve_name = format_ident!("__yog_serve_{}", name);
198 let ctor_name = format_ident!("__yog_ctor_register_{}", name);
199 let (registry, params) = call_params(&func.sig);
200 let Args {
201 ty: args,
202 def: args_def,
203 unpack,
204 ..
205 } = args_of(name, ¶ms);
206 let output = output_type(&func.sig);
207 let doc = doc_of(&func.attrs);
208 let (setup, registry_arg) = if registry {
210 (
211 quote! { let mut __yog_registry = unsafe { ::yog_api::Registry::from_raw(::yog_api::__current_api_ptr() as *const ::yog_api::YogApi) }; },
212 quote! { &mut __yog_registry, },
213 )
214 } else {
215 (quote! {}, quote! {})
216 };
217 TokenStream::from(quote! {
218 #func
219
220 #args_def
221
222 #[doc(hidden)]
223 unsafe extern "C" fn #serve_name(
224 _ud: *mut ::std::os::raw::c_void,
225 payload: ::yog_api::YogBytes,
226 reply: *const ::yog_api::YogReply,
227 ) -> ::yog_api::YogStatus {
228 unsafe {
229 ::yog_api::__yog_serve::<#args, #output>(payload, reply, |a| {
230 #setup
231 #name(#registry_arg #unpack)
232 })
233 }
234 }
235
236 #[::yog_api::ctor::ctor(unsafe, crate_path = ::yog_api::ctor)]
238 #[doc(hidden)]
239 fn #ctor_name() {
240 ::yog_api::__yog_export_registry().lock().push(
241 ::yog_api::YogExportEntry {
242 name: #name_str,
243 handler: #serve_name,
244 doc: #doc,
245 shapes: || (<#args as ::yog_api::yog_data::Schema>::shape(), <#output as ::yog_api::yog_data::Schema>::shape()),
246 implements: &[#(#implements),*],
247 }
248 );
249 }
250 })
251}
252
253#[proc_macro_attribute]
258pub fn yog_mirror(attr: TokenStream, item: TokenStream) -> TokenStream {
259 mirror::expand(attr.into(), item.into())
260 .unwrap_or_else(syn::Error::into_compile_error)
261 .into()
262}
263
264#[proc_macro_attribute]
271pub fn bench(_attr: TokenStream, item: TokenStream) -> TokenStream {
272 let mut f = syn::parse_macro_input!(item as syn::ItemFn);
273 if matches!(f.vis, syn::Visibility::Inherited) {
274 f.vis = syn::parse_quote!(pub(crate));
275 }
276 TokenStream::from(quote! { #f })
277}
278
279#[proc_macro]
286pub fn import(input: TokenStream) -> TokenStream {
287 let import_block = syn::parse_macro_input!(input as ImportBlock);
288 let mod_name = &import_block.mod_name;
289 let mod_ident = format_ident!("{}", mod_name.replace('-', "_"));
290 let mut items = proc_macro2::TokenStream::new();
291 for item in &import_block.items {
292 match item {
293 ImportItem::Func(func) => items.extend(generate_import_fn(func, mod_name)),
294 ImportItem::Type(t) => {
295 let derive = schema_derive();
296 items.extend(quote! { #derive #t });
297 }
298 }
299 }
300 TokenStream::from(quote! { pub mod #mod_ident { use super::*; #items } })
301}
302
303struct ImportFn {
305 vis: syn::Visibility,
306 sig: syn::Signature,
307}
308
309fn generate_import_fn(func: &ImportFn, mod_name: &str) -> proc_macro2::TokenStream {
310 let name = &func.sig.ident;
311 let name_str = name.to_string();
312 let vis = &func.vis;
313 let (_, params) = call_params(&func.sig);
314 let Args {
315 ty: args,
316 def: args_def,
317 pack,
318 ..
319 } = args_of(name, ¶ms);
320 let output = output_type(&func.sig);
321 let names: Vec<&syn::Ident> = params.iter().map(|(n, _)| n).collect();
322 let types: Vec<&syn::Type> = params.iter().map(|(_, t)| t).collect();
323 quote! {
324 #args_def
325
326 #vis fn #name(#(#names: #types),*) -> ::std::option::Option<#output> {
328 ::yog_api::call_export::<#args, #output>(#mod_name, #name_str, #pack)
329 }
330 }
331}
332
333struct ImportBlock {
336 mod_name: String,
337 items: Vec<ImportItem>,
338}
339enum ImportItem {
340 Func(Box<ImportFn>),
341 Type(proc_macro2::TokenStream),
343}
344
345impl syn::parse::Parse for ImportBlock {
346 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
347 let from_kw: syn::Ident = input.parse()?;
348 if from_kw != "from" {
349 return Err(syn::Error::new(from_kw.span(), "expected `from`"));
350 }
351 let lit: syn::LitStr = input.parse()?;
352 let mod_name = lit.value();
353 let content;
354 syn::braced!(content in input);
355 let mut items = Vec::new();
356 while !content.is_empty() {
357 let attrs = content.call(syn::Attribute::parse_outer)?;
358 let vis: syn::Visibility = content.parse()?;
359 if content.peek(syn::Token![fn]) {
360 let sig: syn::Signature = content.parse()?;
361 if content.peek(syn::token::Brace) {
362 let _body: syn::Block = content.parse()?;
363 } else {
364 content.parse::<syn::Token![;]>()?;
365 }
366 items.push(ImportItem::Func(Box::new(ImportFn { vis, sig })));
367 } else if content.peek(syn::Token![enum]) {
368 let mut e: syn::ItemEnum = content.parse()?;
369 e.attrs = attrs;
370 e.vis = vis;
371 items.push(ImportItem::Type(quote! { #e }));
372 } else {
373 let mut s: syn::ItemStruct = content.parse()?;
374 s.attrs = attrs;
375 s.vis = vis;
376 items.push(ImportItem::Type(quote! { #s }));
377 let _ = content.parse::<syn::Token![;]>();
378 }
379 }
380 Ok(ImportBlock { mod_name, items })
381 }
382}