1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4use quote::{format_ident, quote, ToTokens};
5use syn::{Data, DeriveInput, Fields, FieldsNamed, FieldsUnnamed, FnArg, ItemFn, Lit, Meta, MetaNameValue, parse_macro_input, parse_quote, Path, PatType, Signature, Variant, Expr, ExprLit, Attribute};
6use syn::__private::TokenStream2;
7use syn::punctuated::Punctuated;
8use syn::token::Comma;
9
10
11#[proc_macro_attribute]
70pub fn export(_attr: TokenStream, input: TokenStream) -> TokenStream {
71 let input = TokenStream2::from(input);
72 let expanded = quote! {
73 #[doc = "@ferment::export"]
74 #input
75 };
76 TokenStream::from(expanded)
77}
78
79
80#[proc_macro_attribute]
81pub fn register(attr: TokenStream, input: TokenStream) -> TokenStream {
82 let ty = syn::parse::<Path>(attr).expect("Expected a path");
83 let ty_str = quote!(#ty).to_string();
84 let input = TokenStream2::from(input);
85 let expanded = quote! {
86 #[doc = concat!("@ferment::register(", #ty_str, ")")]
87 #[repr(C)]
88 #input
89 };
90 TokenStream::from(expanded)
91}
92
93#[proc_macro_attribute]
94pub fn opaque(_attr: TokenStream, input: TokenStream) -> TokenStream {
95 let input = TokenStream2::from(input);
96 let expanded = quote! {
97 #[doc = "@ferment::opaque"]
98 #input
99 };
100 TokenStream::from(expanded)
101}
102
103
104#[proc_macro_derive(CompositionContext)]
105pub fn composition_context_derive(input: TokenStream) -> TokenStream {
106 let input = parse_macro_input!(input as DeriveInput);
107 let name = &input.ident;
108 let expanded = quote!(impl crate::composable::CompositionContext for #name {});
109 TokenStream::from(expanded)
110}
111
112#[proc_macro_derive(MethodCall, attributes(namespace))]
113pub fn method_call_derive(input: TokenStream) -> TokenStream {
114 let input = parse_macro_input!(input as DeriveInput);
115 let name = input.ident;
116 let namespace = input.attrs.iter()
117 .find_map(|Attribute { ref meta, .. }| {
118 if let Meta::NameValue(MetaNameValue { path, value: Expr::Lit(ExprLit { lit: Lit::Str(s), .. }), .. }) = meta {
119 path.is_ident("namespace").then(|| syn::parse_str::<Path>(&s.value()).expect("Invalid namespace"))
120 } else {
121 None
122 }
123 })
124 .expect("namespace attribute is required");
125
126 let expression_enum_name = format_ident!("{name}Expr");
127 let mut expression_variants = Punctuated::<TokenStream2, Comma>::new();
128 let mut methods = Punctuated::<TokenStream2, Comma>::new();
129 let mut exprs = Punctuated::<TokenStream2, Comma>::new();
130
131 if let Data::Enum(data) = &input.data {
132 for Variant { ident, fields, .. } in &data.variants {
133 match fields {
134 Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => {
135 let field_count = unnamed.len();
136 let field_names = (0..field_count).map(|i| format_ident!("field{i}")).collect::<Vec<_>>();
137 let field_types = unnamed.iter().map(|f| &f.ty).collect::<Vec<_>>();
138 expression_variants.push(quote!(#ident(#(#field_types,)* T)));
139 methods.push(quote!(#expression_enum_name::#ident(#(#field_names,)* _) => #name::#ident(#(#field_names.clone(),)*).to_token_stream()));
140 exprs.push(quote!(#expression_enum_name::#ident(#(#field_names,)* expr) => expr.to_token_stream()));
141 },
142 Fields::Named(FieldsNamed { named, .. }) => {
143 let field_names = named.iter().filter_map(|f| f.ident.clone()).collect::<Vec<_>>();
144 let field_types = named.iter().map(|f| &f.ty).collect::<Vec<_>>();
145 expression_variants.push(quote!(#ident { #(#field_names: #field_types,)* expr: T }));
146 methods.push(quote!(#expression_enum_name::#ident { #(#field_names,)* .. } => #name::#ident { #(#field_names: #field_names.clone(),)* }.to_token_stream()));
147 exprs.push(quote!(#expression_enum_name::#ident { #(#field_names,)* expr } => expr.to_token_stream()));
148 },
149 Fields::Unit => {
150 expression_variants.push(quote!(#ident(T)));
151 methods.push(quote!(#expression_enum_name::#ident(_) => #name::#ident.to_token_stream()));
152 exprs.push(quote!(#expression_enum_name::#ident(expr) => expr.to_token_stream()));
153 }
154 }
155 }
156 }
157
158 let expanded = quote! {
159 #[derive(Clone, Debug)]
160 pub enum #expression_enum_name<T: quote::ToTokens> {
161 #expression_variants
162 }
163 impl<T: quote::ToTokens> crate::presentation::MethodCall for #expression_enum_name<T> {
164 fn method(&self) -> TokenStream2 {
165 let mut tokens = TokenStream2::new();
166 let method = match self {
167 #methods
168 };
169 let ns = syn::punctuated::Punctuated::<_, syn::token::PathSep>::from_iter([quote!(#namespace), method]);
170 tokens.append_all(vec![ns.to_token_stream()]);
171 tokens
172 }
173 fn expr(&self) -> TokenStream2 {
174 match self {
175 #exprs
176 }
177 }
178 }
179 impl<T: quote::ToTokens + 'static> ToTokens for #expression_enum_name<T> {
180 fn to_tokens(&self, dst: &mut TokenStream2) {
181 (self as &dyn crate::presentation::MethodCall).to_tokens(dst)
182 }
183 }
184 };
185 TokenStream::from(expanded)
186}
187
188#[proc_macro_derive(Display)]
189pub fn to_string_derive(input: TokenStream) -> TokenStream {
190 let input = parse_macro_input!(input as DeriveInput);
191 let name = input.ident;
192 let data = match input.data {
193 Data::Enum(data) => data,
194 _ => panic!("#[derive(ToString)] is only defined for enums"),
195 };
196 let match_arms = data.variants.iter().map(|Variant { ident, fields, .. } | {
197 match fields {
198 Fields::Named(fields) => quote! { Self::#ident { .. } => format!("{}{}", stringify!(#ident), stringify!(#fields)), },
199 Fields::Unnamed(fields) => quote! { Self::#ident(..) => format!("{}{}", stringify!(#ident), stringify!(#fields)), },
200 Fields::Unit => quote! { Self::#ident => format!("{}", stringify!(#ident)), }
201 }
202 });
203 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
204 let expanded = quote! {
205 impl #impl_generics std::fmt::Display for #name #ty_generics #where_clause {
206 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
207 f.write_str(match self {
208 #(#match_arms)*
209 }.as_str())
210 }
211 }
212 };
213 TokenStream::from(expanded)
214}
215
216#[proc_macro_derive(BasicComposerOwner)]
217pub fn basic_composer_owner_derive(input: TokenStream) -> TokenStream {
218 let DeriveInput { ident, generics, .. } = parse_macro_input!(input as DeriveInput);
219 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
220 let expanded = quote! {
221 impl #impl_generics crate::composer::BasicComposerOwner<crate::presentable::Context, SPEC, Gen> for #ident #ty_generics #where_clause {
222 fn base(&self) -> &crate::composer::BasicComposer<crate::composer::ParentComposer<Self>> {
223 &self.base
224 }
225 }
226 };
227 TokenStream::from(expanded)
228}
229#[proc_macro_derive(ComposerBase)]
230pub fn composer_base_derive(input: TokenStream) -> TokenStream {
231 let DeriveInput { ident, generics, .. } = parse_macro_input!(input as DeriveInput);
232 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
233 let expanded = quote! {
234 impl #impl_generics crate::composer::BasicComposerOwner<SPEC> for #ident #ty_generics #where_clause {
235 fn base(&self) -> &crate::composer::BasicComposerLink<SPEC, Self> {
236 &self.base
237 }
238 }
239 impl #impl_generics crate::composer::AttrComposable<SPEC::Attr> for #ident #ty_generics #where_clause {
240 fn compose_attributes(&self) -> SPEC::Attr {
241 self.base().compose_attributes()
242 }
243 }
244 impl #impl_generics crate::composer::GenericsComposable<SPEC::Gen> for #ident #ty_generics #where_clause {
245 fn compose_generics(&self) -> SPEC::Gen {
246 self.base().compose_generics()
247 }
248 }
249 impl #impl_generics crate::composer::LifetimesComposable<SPEC::Lt> for #ident #ty_generics #where_clause {
250 fn compose_lifetimes(&self) -> SPEC::Lt {
251 self.base().compose_lifetimes()
252 }
253 }
254 impl #impl_generics crate::composer::SourceAccessible for #ident #ty_generics #where_clause {
255 fn context(&self) -> &ComposerLink<crate::context::ScopeContext> {
256 self.base().context()
257 }
258 }
259 impl #impl_generics crate::composer::TypeAspect<SPEC::TYC> for #ident #ty_generics #where_clause {
260 fn type_context_ref(&self) -> &SPEC::TYC {
261 self.base().type_context_ref()
262 }
263 }
264 };
265 TokenStream::from(expanded)
266}
267
268#[proc_macro_attribute]
269pub fn debug_io(_attr: TokenStream, item: TokenStream) -> TokenStream {
270 let input = parse_macro_input!(item as ItemFn);
271 let ItemFn { sig: Signature { ident: ref method_name, ref inputs, .. }, ref block, .. } = input;
272 let args: Vec<_> = inputs.iter().filter_map(|arg| {
273 if let FnArg::Typed(PatType { pat, .. }) = arg {
274 Some(quote! { #pat })
275 } else {
276 None
277 }
278 }).collect();
279 let fn_name = format!("{}", method_name);
280
281 let args_str = args.iter().map(ToTokens::to_token_stream).collect::<Vec<_>>();
282 let new_block = quote! {{
283 let debug_str = #fn_name;
284 let result = {
285 #block
286 };
287 println!("{}({:?}) -> {:?}", debug_str, #(#args_str),*, result);
288 result
289 }};
290 let mut output = input.clone();
291
292 output.block = parse_quote!(#new_block);
293 TokenStream::from(quote! { #output })
294}
295
296
297