pipeit_derive/
lib.rs

1use proc_macro::TokenStream;
2use quote::{quote, format_ident};
3use syn::{parse_macro_input, ItemFn, LitStr};
4
5// FNV-1a 64-bit hash
6const fn hash_str(s: &str) -> u64 {
7    let mut hash = 0xcbf29ce484222325;
8    let bytes = s.as_bytes();
9    let mut i = 0;
10    while i < bytes.len() {
11        hash ^= bytes[i] as u64;
12        hash = hash.wrapping_mul(0x100000001b3);
13        i += 1;
14    }
15    hash
16}
17
18/// Attribute macro to tag a function as a pipeline node.
19/// Usage: #[node("name")]
20#[proc_macro_attribute]
21pub fn node(attr: TokenStream, item: TokenStream) -> TokenStream {
22    let name_lit = parse_macro_input!(attr as LitStr);
23    let name_str = name_lit.value();
24    let hash = hash_str(&name_str);
25    let tag_ident = format_ident!("__pipeline_tag_{}", hash);
26    
27    let input_fn = parse_macro_input!(item as ItemFn);
28    let fn_name = &input_fn.sig.ident;
29    let fn_vis = &input_fn.vis;
30
31    // Analyze inputs to deduce types.
32    let args = &input_fn.sig.inputs;
33    let mut arg_types = Vec::new();
34    let mut arg_names = Vec::new();
35    for (i, arg) in args.iter().enumerate() {
36        if let syn::FnArg::Typed(pat_type) = arg {
37            arg_types.push(pat_type.ty.clone());
38            arg_names.push(format_ident!("arg_{}", i));
39        }
40    }
41    
42    let ret_type = match &input_fn.sig.output {
43        syn::ReturnType::Default => quote! { () },
44        syn::ReturnType::Type(_, ty) => quote! { #ty },
45    };
46
47    let expanded = quote! {
48        #input_fn
49
50        #[allow(non_camel_case_types)]
51        #[derive(Clone, Copy)]
52        #fn_vis struct #tag_ident;
53
54        impl<I> pipe_it::handler::Handler<I, #ret_type, (#(#arg_types),*)> 
55        for #tag_ident
56        where
57             I: Clone + Send + Sync + 'static,
58             #ret_type: Send + 'static,
59             #(
60                #arg_types: pipe_it::FromContext<I>,
61             )*
62        {
63            fn call(&self, ctx: pipe_it::Context<I>) -> impl std::future::Future<Output = #ret_type> + Send {
64                async move {
65                    let args = {
66                        let c = ctx;
67                        (
68                            #(
69                                <#arg_types as pipe_it::FromContext<I>>::from(c.clone()).await
70                            ),*
71                        )
72                    };
73                    let (#(#arg_names),*) = args;
74                    #fn_name(
75                        #(#arg_names),*
76                    ).await
77                }
78            }
79        }
80    };
81
82    TokenStream::from(expanded)
83}
84
85/// Function-like macro to generate the tag struct name.
86/// Usage: tag_struct!("name") -> __pipeline_tag_HASH
87#[proc_macro]
88pub fn tag_struct(input: TokenStream) -> TokenStream {
89    let lit = parse_macro_input!(input as LitStr);
90    let hash = hash_str(&lit.value());
91    let ident = format_ident!("__pipeline_tag_{}", hash);
92    quote! { #ident }.into()
93}
94
95/// Function-like macro to get the hash ID of a tag name.
96#[proc_macro]
97pub fn tag_id(input: TokenStream) -> TokenStream {
98    let lit = parse_macro_input!(input as LitStr);
99    let hash = hash_str(&lit.value());
100    quote! { #hash }.into()
101}