1use proc_macro::TokenStream;
9use quote::{format_ident, quote};
10
11mod inject;
12mod mirror;
13mod nanite;
14
15fn schema_derive() -> proc_macro2::TokenStream {
20 quote! {
21 #[derive(::yog_api::yog_data::Schema)]
22 #[data(crate = "::yog_api::yog_data")]
23 }
24}
25
26fn enum_tag(e: &syn::ItemEnum) -> proc_macro2::TokenStream {
29 use yog_ids::data_attr::{DATA, TAG};
30 let has_data = e
31 .variants
32 .iter()
33 .any(|v| !matches!(v.fields, syn::Fields::Unit));
34 let tagged = e.attrs.iter().filter(|a| a.path().is_ident(DATA)).any(|a| {
35 let mut tag = false;
36 let _ = a.parse_nested_meta(|m| {
38 tag |= m.path.is_ident(TAG);
39 if m.input.peek(syn::Token![=]) {
40 m.value()?.parse::<syn::Expr>()?;
41 }
42 Ok(())
43 });
44 tag
45 });
46 if !has_data || tagged {
47 return quote! {};
48 }
49 let tag = yog_ids::word::TYPE;
50 let (data, tag_key) = (format_ident!("{DATA}"), format_ident!("{TAG}"));
51 quote! { #[#data(#tag_key = #tag)] }
52}
53
54fn is_registry_ty(ty: &syn::Type) -> bool {
57 let ty = match ty {
58 syn::Type::Reference(r) => &*r.elem,
59 other => other,
60 };
61 quote! { #ty }
62 .to_string()
63 .replace(' ', "")
64 .ends_with("Registry")
65}
66
67fn call_params(sig: &syn::Signature) -> (bool, Vec<(syn::Ident, syn::Type)>) {
70 let mut typed: Vec<&syn::PatType> = sig
71 .inputs
72 .iter()
73 .filter_map(|a| match a {
74 syn::FnArg::Typed(pt) => Some(pt),
75 syn::FnArg::Receiver(_) => None,
76 })
77 .collect();
78 let registry = typed.first().is_some_and(|pt| is_registry_ty(&pt.ty));
79 if registry {
80 typed.remove(0);
81 }
82 let params = typed
83 .iter()
84 .enumerate()
85 .map(|(i, pt)| {
86 let name = match &*pt.pat {
87 syn::Pat::Ident(p) => p.ident.clone(),
88 _ => format_ident!("__yog_arg{}", i),
89 };
90 (name, (*pt.ty).clone())
91 })
92 .collect();
93 (registry, params)
94}
95
96struct Args {
100 ty: proc_macro2::TokenStream,
102 def: proc_macro2::TokenStream,
104 pack: proc_macro2::TokenStream,
106 unpack: proc_macro2::TokenStream,
108}
109
110fn args_of(name: &syn::Ident, params: &[(syn::Ident, syn::Type)]) -> Args {
111 if let [(param, ty)] = params {
112 return Args {
113 ty: quote! { #ty },
114 def: quote! {},
115 pack: quote! { &#param },
116 unpack: quote! { a },
117 };
118 }
119 let args = format_ident!("__YogArgs_{}", name);
120 let fields = params.iter().map(|(n, t)| quote! { #n: #t });
121 let names: Vec<&syn::Ident> = params.iter().map(|(n, _)| n).collect();
122 let derive = schema_derive();
123 Args {
124 ty: quote! { #args },
125 def: quote! {
126 #[doc(hidden)]
127 #[allow(non_camel_case_types)]
129 #derive
130 struct #args { #(#fields),* }
131 },
132 pack: quote! { &#args { #(#names),* } },
133 unpack: quote! { #(a.#names),* },
134 }
135}
136
137fn output_type(sig: &syn::Signature) -> proc_macro2::TokenStream {
138 match &sig.output {
139 syn::ReturnType::Default => quote! { () },
140 syn::ReturnType::Type(_, ty) => quote! { #ty },
141 }
142}
143
144fn doc_of(attrs: &[syn::Attribute]) -> String {
148 let lines: Vec<String> = attrs
149 .iter()
150 .filter(|a| a.path().is_ident("doc"))
151 .filter_map(|a| match &a.meta {
152 syn::Meta::NameValue(nv) => match &nv.value {
153 syn::Expr::Lit(syn::ExprLit {
154 lit: syn::Lit::Str(s),
155 ..
156 }) => Some(s.value().trim().to_owned()),
157 _ => None,
158 },
159 _ => None,
160 })
161 .collect();
162 lines.join(" ")
163}
164
165fn implements_of(attr: TokenStream) -> syn::Result<Vec<String>> {
167 let mut out = Vec::new();
168 let parser = syn::meta::parser(|meta| {
169 if meta.path.is_ident("implements") {
170 out.push(meta.value()?.parse::<syn::LitStr>()?.value());
171 Ok(())
172 } else {
173 Err(meta.error("unknown `yog_export` argument (implements)"))
174 }
175 });
176 syn::parse::Parser::parse(parser, attr)?;
177 Ok(out)
178}
179
180#[proc_macro_attribute]
181pub fn yog_export(attr: TokenStream, item: TokenStream) -> TokenStream {
182 let implements = match implements_of(attr) {
183 Ok(i) => i,
184 Err(e) => return e.to_compile_error().into(),
185 };
186 let tokens = proc_macro2::TokenStream::from(item.clone());
187 if syn::parse2::<syn::ItemStruct>(tokens.clone()).is_ok() {
188 let derive = schema_derive();
189 return TokenStream::from(quote! { #derive #tokens });
190 }
191 if let Ok(e) = syn::parse2::<syn::ItemEnum>(tokens.clone()) {
192 let derive = schema_derive();
193 let tag = enum_tag(&e);
194 return TokenStream::from(quote! { #derive #tag #tokens });
195 }
196 let func = syn::parse_macro_input!(item as syn::ItemFn);
197 let name = &func.sig.ident;
198 let name_str = name.to_string();
199 let serve_name = format_ident!("__yog_serve_{}", name);
200 let ctor_name = format_ident!("__yog_ctor_register_{}", name);
201 let (registry, params) = call_params(&func.sig);
202 let Args {
203 ty: args,
204 def: args_def,
205 unpack,
206 ..
207 } = args_of(name, ¶ms);
208 let output = output_type(&func.sig);
209 let doc = doc_of(&func.attrs);
210 let (setup, registry_arg) = if registry {
212 (
213 quote! { let mut __yog_registry = unsafe { ::yog_api::Registry::from_raw(::yog_api::__current_api_ptr() as *const ::yog_api::YogApi) }; },
214 quote! { &mut __yog_registry, },
215 )
216 } else {
217 (quote! {}, quote! {})
218 };
219 TokenStream::from(quote! {
220 #func
221
222 #args_def
223
224 #[doc(hidden)]
225 unsafe extern "C" fn #serve_name(
226 _ud: *mut ::std::os::raw::c_void,
227 payload: ::yog_api::YogBytes,
228 reply: *const ::yog_api::YogReply,
229 ) -> ::yog_api::YogStatus {
230 unsafe {
231 ::yog_api::__yog_serve::<#args, #output>(payload, reply, |a| {
232 #setup
233 #name(#registry_arg #unpack)
234 })
235 }
236 }
237
238 #[::yog_api::ctor::ctor(unsafe, crate_path = ::yog_api::ctor)]
240 #[doc(hidden)]
241 fn #ctor_name() {
242 ::yog_api::__yog_export_registry().lock().push(
243 ::yog_api::YogExportEntry {
244 name: #name_str,
245 handler: #serve_name,
246 doc: #doc,
247 shapes: || (<#args as ::yog_api::yog_data::Schema>::shape(), <#output as ::yog_api::yog_data::Schema>::shape()),
248 implements: &[#(#implements),*],
249 }
250 );
251 }
252 })
253}
254
255#[proc_macro_attribute]
260pub fn yog_mirror(attr: TokenStream, item: TokenStream) -> TokenStream {
261 mirror::expand(attr.into(), item.into())
262 .unwrap_or_else(syn::Error::into_compile_error)
263 .into()
264}
265
266#[proc_macro_attribute]
272pub fn nanite(attr: TokenStream, item: TokenStream) -> TokenStream {
273 let item = proc_macro2::TokenStream::from(item);
274 let out = if attr.is_empty() {
275 nanite::members(item)
276 } else {
277 nanite::class(attr.into(), item)
278 };
279 out.unwrap_or_else(syn::Error::into_compile_error).into()
280}
281
282macro_rules! nanites {
284 ($($(#[$doc:meta])* $name:ident => $kind:ident, $at:ident, $cancellable:literal;)*) => {$(
285 $(#[$doc])*
286 #[proc_macro_attribute]
287 pub fn $name(attr: TokenStream, item: TokenStream) -> TokenStream {
288 let kind = inject::Kind {
289 kind: stringify!($kind),
290 at: stringify!($at),
291 cancellable: $cancellable,
292 };
293 inject::expand(&kind, attr.into(), item.into())
294 .unwrap_or_else(syn::Error::into_compile_error)
295 .into()
296 }
297 )*};
298}
299
300nanites! {
301 inject => INJECT, HEAD, false;
303 modify_return => INJECT, RETURN, true;
305 overwrite => INJECT, HEAD, true;
307 redirect => REDIRECT, INVOKE, false;
309 wrap => REDIRECT, INVOKE, false;
311 modify_arg => MODIFY_ARG, INVOKE, false;
313 modify_variable => MODIFY_VARIABLE, STORE, false;
315 modify_constant => MODIFY_CONSTANT, HEAD, false;
317 add_method => ADD_METHOD, HEAD, false;
320}
321
322#[proc_macro_attribute]
329pub fn bench(_attr: TokenStream, item: TokenStream) -> TokenStream {
330 let mut f = syn::parse_macro_input!(item as syn::ItemFn);
331 if matches!(f.vis, syn::Visibility::Inherited) {
332 f.vis = syn::parse_quote!(pub(crate));
333 }
334 TokenStream::from(quote! { #f })
335}
336
337#[proc_macro]
344pub fn import(input: TokenStream) -> TokenStream {
345 let import_block = syn::parse_macro_input!(input as ImportBlock);
346 let mod_name = &import_block.mod_name;
347 let mod_ident = format_ident!("{}", mod_name.replace('-', "_"));
348 let mut items = proc_macro2::TokenStream::new();
349 for item in &import_block.items {
350 match item {
351 ImportItem::Func(func) => items.extend(generate_import_fn(func, mod_name)),
352 ImportItem::Type(t) => {
353 let derive = schema_derive();
354 items.extend(quote! { #derive #t });
355 }
356 }
357 }
358 TokenStream::from(quote! { pub mod #mod_ident { use super::*; #items } })
359}
360
361struct ImportFn {
363 vis: syn::Visibility,
364 sig: syn::Signature,
365}
366
367fn generate_import_fn(func: &ImportFn, mod_name: &str) -> proc_macro2::TokenStream {
368 let name = &func.sig.ident;
369 let name_str = name.to_string();
370 let vis = &func.vis;
371 let (_, params) = call_params(&func.sig);
372 let Args {
373 ty: args,
374 def: args_def,
375 pack,
376 ..
377 } = args_of(name, ¶ms);
378 let output = output_type(&func.sig);
379 let names: Vec<&syn::Ident> = params.iter().map(|(n, _)| n).collect();
380 let types: Vec<&syn::Type> = params.iter().map(|(_, t)| t).collect();
381 quote! {
382 #args_def
383
384 #vis fn #name(#(#names: #types),*) -> ::std::option::Option<#output> {
386 ::yog_api::call_export::<#args, #output>(#mod_name, #name_str, #pack)
387 }
388 }
389}
390
391struct ImportBlock {
394 mod_name: String,
395 items: Vec<ImportItem>,
396}
397enum ImportItem {
398 Func(Box<ImportFn>),
399 Type(proc_macro2::TokenStream),
401}
402
403impl syn::parse::Parse for ImportBlock {
404 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
405 let from_kw: syn::Ident = input.parse()?;
406 if from_kw != "from" {
407 return Err(syn::Error::new(from_kw.span(), "expected `from`"));
408 }
409 let lit: syn::LitStr = input.parse()?;
410 let mod_name = lit.value();
411 let content;
412 syn::braced!(content in input);
413 let mut items = Vec::new();
414 while !content.is_empty() {
415 let attrs = content.call(syn::Attribute::parse_outer)?;
416 let vis: syn::Visibility = content.parse()?;
417 if content.peek(syn::Token![fn]) {
418 let sig: syn::Signature = content.parse()?;
419 if content.peek(syn::token::Brace) {
420 let _body: syn::Block = content.parse()?;
421 } else {
422 content.parse::<syn::Token![;]>()?;
423 }
424 items.push(ImportItem::Func(Box::new(ImportFn { vis, sig })));
425 } else if content.peek(syn::Token![enum]) {
426 let mut e: syn::ItemEnum = content.parse()?;
427 e.attrs = attrs;
428 e.vis = vis;
429 items.push(ImportItem::Type(quote! { #e }));
430 } else {
431 let mut s: syn::ItemStruct = content.parse()?;
432 s.attrs = attrs;
433 s.vis = vis;
434 items.push(ImportItem::Type(quote! { #s }));
435 let _ = content.parse::<syn::Token![;]>();
436 }
437 }
438 Ok(ImportBlock { mod_name, items })
439 }
440}