1use std::borrow::Cow;
2
3use proc_macro2::{Punct, Spacing, Span, TokenStream};
4use quote::{format_ident, quote, quote_spanned, ToTokens};
5use syn::{parse_quote, parse_quote_spanned, spanned::Spanned as _, Ident, LitInt, Path};
6
7use crate::{find_and_replace::SingleToken, Telety};
8
9pub(crate) type GenerateMacroTokens = fn(tele_ty: &Telety) -> Option<TokenStream>;
10
11pub struct Command {
13 version: usize,
14 keyword: &'static str,
15 generate_macro_tokens: GenerateMacroTokens,
16}
17
18impl Command {
19 pub(crate) const fn new(
20 version: usize,
21 keyword: &'static str,
22 generate_macro_tokens: GenerateMacroTokens,
23 ) -> Self {
24 Self {
25 version,
26 keyword,
27 generate_macro_tokens,
28 }
29 }
30
31 pub(crate) const fn version(&self) -> usize {
32 self.version
33 }
34
35 fn version_lit(&self, span: Option<Span>) -> LitInt {
36 let span = span.unwrap_or(Span::call_site());
37 LitInt::new(&self.version().to_string(), span)
38 }
39
40 fn keyword(&self, span: Option<Span>) -> Ident {
41 let span = span.unwrap_or(Span::call_site());
42 Ident::new(self.keyword, span)
43 }
44
45 #[doc(hidden)]
46 pub fn generate_macro_arm(&self, ty: &Telety) -> syn::Result<Option<TokenStream>> {
47 if let Some(implementation) = (self.generate_macro_tokens)(ty) {
48 let span = ty.item().span();
49
50 let ParameterIdents {
51 args,
52 needle,
53 haystack,
54 } = ParameterIdents::new(span);
55
56 let keyword = self.keyword(Some(span));
57 let version = self.version_lit(Some(span));
58 Ok(Some(quote_spanned! { span =>
59 (#version, #keyword $( ( $($#args:tt)* ) )?, $#needle:tt, $($#haystack:tt)*) => {
60 #implementation
61 };
62 }))
63 } else {
64 Ok(None)
65 }
66 }
67
68 pub fn apply(
96 &'static self,
97 macro_path: Path,
98 needle: impl Into<SingleToken>,
99 haystack: impl ToTokens,
100 ) -> Apply {
101 Apply::new(
102 self,
103 macro_path,
104 needle.into(),
105 haystack.into_token_stream(),
106 )
107 }
108}
109
110pub(crate) struct ParameterIdents {
111 pub args: Ident,
112 pub needle: Ident,
113 pub haystack: Ident,
114}
115
116impl ParameterIdents {
117 pub fn new(span: Span) -> Self {
118 Self {
119 args: Ident::new("args", span),
120 needle: Ident::new("needle", span),
121 haystack: Ident::new("haystack", span),
122 }
123 }
124}
125
126pub struct Apply {
129 command: &'static Command,
130 macro_path: Path,
131 needle: SingleToken,
132 haystack: TokenStream,
133 args: Option<TokenStream>,
134 fallback: Option<TokenStream>,
135 telety_path: Option<Path>,
136 unique_macro_ident: Option<Ident>,
137}
138
139impl Apply {
140 fn new(
141 command: &'static Command,
142 macro_path: Path,
143 needle: SingleToken,
144 haystack: TokenStream,
145 ) -> Self {
146 Self {
147 command,
148 macro_path,
149 needle,
150 haystack,
151 args: None,
152 fallback: None,
153 telety_path: None,
154 unique_macro_ident: None,
155 }
156 }
157
158 #[doc(hidden)]
159 pub fn with_arguments(mut self, arguments: impl ToTokens) -> Self {
162 self.args.replace(arguments.into_token_stream());
163 self
164 }
165
166 pub fn with_fallback(mut self, fallback: impl ToTokens) -> Self {
171 self.fallback.replace(fallback.into_token_stream());
172 self
173 }
174
175 pub fn with_telety_path(mut self, telety_path: Path) -> Self {
179 self.telety_path.replace(telety_path);
180 self
181 }
182
183 pub fn with_macro_forwarding(mut self, unique_macro_ident: Ident) -> Self {
190 self.unique_macro_ident.replace(unique_macro_ident);
191 self
192 }
193}
194
195impl ToTokens for Apply {
196 fn to_tokens(&self, tokens: &mut TokenStream) {
197 let macro_path = &self.macro_path;
198 let needle = &self.needle;
199 let mut haystack = self.haystack.to_token_stream();
200 let args = self.args.as_ref().map(|ts| quote!((#ts)));
201
202 let span = self.haystack.span();
203 let version = self.command.version_lit(Some(span));
204 let keyword = self.command.keyword(Some(span));
205
206 let textual_macro_ident: Ident = format_ident!(
207 "my_macro_{}",
208 self.unique_macro_ident
209 .as_ref()
210 .unwrap_or(&format_ident!("a"))
211 );
212
213 let mut fallback = self.fallback.as_ref().map(|f| f.to_token_stream());
214
215 if let Some(fallback) = fallback.as_mut() {
216 if let Some(unique_macro_ident) = &self.unique_macro_ident {
217 let macro_wrapper = |contents: &TokenStream| {
218 let contents = crate::find_and_replace::find_and_replace(
221 Punct::new('$', Spacing::Alone),
222 quote!($dollar dollar),
223 contents.into_token_stream(),
224 );
225
226 quote_spanned! { span =>
227 #[doc(hidden)]
229 #[macro_export]
230 macro_rules! #unique_macro_ident {
231 ($dollar:tt) => {
232 macro_rules! #textual_macro_ident {
234 ($dollar dollar:tt) => {
235 #contents
237 };
238 }
239 };
240 }
241 }
242 };
243
244 *fallback = macro_wrapper(fallback);
245 haystack = macro_wrapper(&haystack);
246 }
247 }
248
249 let mut output = parse_quote_spanned! { span =>
250 #macro_path! { #version, #keyword #args, #needle, #haystack }
251 };
252
253 if let Some(fallback) = fallback {
254 let telety_path = self
255 .telety_path
256 .as_ref()
257 .map(Cow::Borrowed)
258 .unwrap_or_else(|| Cow::Owned(parse_quote!(::telety)));
259
260 output = parse_quote_spanned! { span =>
261 #telety_path::util::try_invoke! {
262 #output
263 #fallback
264 }
265 };
266
267 if let Some(unique_macro_ident) = &self.unique_macro_ident {
268 let temp_ident = format_ident!("_{unique_macro_ident}");
269
270 let import = quote_spanned! { span =>
277 #[macro_use]
278 #[doc(hidden)]
279 mod #temp_ident {
280 pub(super) use crate::*;
281
282 #unique_macro_ident!($);
283 }
284 };
285
286 let invoke = quote_spanned! { span =>
287 #textual_macro_ident! { $ }
288 };
289
290 output = quote_spanned! { span =>
291 #output
292 #import
293 #invoke
294 };
295 }
296 }
297
298 output.to_tokens(tokens);
299 }
300}