1use proc_macro::TokenStream;
2use proc_macro2::{Span, TokenStream as TokenStream2};
3use quote::quote;
4use syn::{
5 Attribute, Expr, Ident, LitStr, Meta, Token,
6 parse::{Parse, ParseStream, Parser},
7 parse_macro_input,
8 punctuated::Punctuated,
9};
10
11#[proc_macro_attribute]
36pub fn mq_fn(attr: TokenStream, item: TokenStream) -> TokenStream {
37 let item_fn = parse_macro_input!(item as syn::ItemFn);
38
39 let parser = Punctuated::<Meta, Token![,]>::parse_terminated;
40 let metas = match parser.parse(attr) {
41 Ok(m) => m,
42 Err(e) => return e.to_compile_error().into(),
43 };
44
45 let mut name_lit: Option<LitStr> = None;
46 let mut params_expr: Option<Expr> = None;
47
48 for meta in &metas {
49 match meta {
50 Meta::NameValue(nv) if nv.path.is_ident("name") => {
51 if let Expr::Lit(syn::ExprLit {
52 lit: syn::Lit::Str(s), ..
53 }) = &nv.value
54 {
55 name_lit = Some(s.clone());
56 }
57 }
58 Meta::NameValue(nv) if nv.path.is_ident("params") => {
59 params_expr = Some(nv.value.clone());
60 }
61 _ => {
62 return syn::Error::new_spanned(meta, "unknown mq_fn attribute key")
63 .to_compile_error()
64 .into();
65 }
66 }
67 }
68
69 let name = match name_lit {
70 Some(n) => n,
71 None => {
72 return syn::Error::new(Span::call_site(), "mq_fn requires `name = \"...\"`")
73 .to_compile_error()
74 .into();
75 }
76 };
77
78 let params = match params_expr {
79 Some(p) => p,
80 None => {
81 return syn::Error::new(Span::call_site(), "mq_fn requires `params = ...`")
82 .to_compile_error()
83 .into();
84 }
85 };
86
87 let fn_ident = &item_fn.sig.ident;
88 let static_name = name.value().to_uppercase();
89 let static_ident = Ident::new(&static_name, Span::call_site());
90
91 let cfg_attrs: Vec<&Attribute> = item_fn.attrs.iter().filter(|a| a.path().is_ident("cfg")).collect();
92
93 quote! {
94 #item_fn
95
96 #(#cfg_attrs)*
97 #[allow(non_upper_case_globals)]
98 static #static_ident: ::std::sync::LazyLock<BuiltinFunction> =
99 ::std::sync::LazyLock::new(|| BuiltinFunction::new(#name, ParamNum::#params, #fn_ident));
100 }
101 .into()
102}
103
104struct BuiltinEntry {
105 attrs: Vec<Attribute>,
106 ident: Ident,
107}
108
109impl Parse for BuiltinEntry {
110 fn parse(input: ParseStream) -> syn::Result<Self> {
111 Ok(BuiltinEntry {
112 attrs: input.call(Attribute::parse_outer)?,
113 ident: input.parse()?,
114 })
115 }
116}
117
118struct BuiltinDispatchInput {
119 entries: Punctuated<BuiltinEntry, Token![,]>,
120}
121
122impl Parse for BuiltinDispatchInput {
123 fn parse(input: ParseStream) -> syn::Result<Self> {
124 Ok(BuiltinDispatchInput {
125 entries: Punctuated::parse_terminated(input)?,
126 })
127 }
128}
129
130#[proc_macro]
152pub fn builtin_dispatch(input: TokenStream) -> TokenStream {
153 let BuiltinDispatchInput { entries } = parse_macro_input!(input as BuiltinDispatchInput);
154
155 let mut hash_consts: Vec<TokenStream2> = Vec::with_capacity(entries.len());
156 let mut match_arms: Vec<TokenStream2> = Vec::with_capacity(entries.len());
157
158 for entry in &entries {
159 let ident = &entry.ident;
160 let name_str = ident.to_string().to_lowercase();
161 let hash_name = format!("HASH_{}", ident.to_string().trim_start_matches('_'));
162 let hash_ident = Ident::new(&hash_name, ident.span());
163 let attrs = &entry.attrs;
164
165 hash_consts.push(quote! {
166 #(#attrs)*
167 const #hash_ident: u64 = fnv1a_hash_64(#name_str);
168 });
169
170 match_arms.push(quote! {
171 #(#attrs)*
172 #hash_ident => Some(&#ident),
173 });
174 }
175
176 quote! {
177 #(#hash_consts)*
178
179 pub fn get_builtin_functions_by_str(name_str: &str) -> Option<&'static BuiltinFunction> {
180 match fnv1a_hash_64(name_str) {
181 #(#match_arms)*
182 _ => None,
183 }
184 .filter(|func| func.name == name_str)
185 .map(|v| &**v)
186 }
187 }
188 .into()
189}