ferment_macro/
lib.rs

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/// The `export` procedural macro facilitates FFI (Foreign Function Interface) kind
12/// for a given function. It handles both input arguments and output types, converting them into a format
13/// suitable for FFI boundaries.
14///
15/// # Syntax
16///
17/// The macro can be applied to any Rust function:
18///
19/// ```ignore
20/// #[ferment_macro::export]
21/// pub fn my_function(arg1: MyType1, arg2: MyType2) -> MyReturnType {
22///     // function implementation
23/// }
24/// ```
25///
26/// # Output
27///
28/// The macro will automatically generate additional FFI-compatible code around the annotated function.
29/// It converts the function into a form that can be easily invoked from C/C++ code.
30///
31/// ## Safety
32///
33/// This macro generates safety documentation specific to the function, covering the expectations
34/// and constraints of the FFI boundary.
35///
36/// ## Function Conversion
37///
38/// The macro processes the function's input arguments and return type, performing necessary transformations
39/// like memory allocation/deallocation, pointer kind, etc., to make them FFI-compatible.
40///
41/// # Panics
42///
43/// - The macro will panic if any of the function's argument types are not supported for kind.
44/// - The macro will also panic if the function's return type is not supported for kind.
45///
46/// # Example
47///
48/// ```ignore
49/// #[ferment_macro::export]
50/// pub fn add(a: i32, b: i32) -> i32 {
51///     a + b
52/// }
53/// ```
54///
55/// After applying the macro, the function can be safely invoked from C/C++ code.
56///
57/// # Note
58///
59/// This macro is intended for internal use and should be used cautiously,
60/// understanding the risks associated with FFI calls.
61///
62/// # See Also
63///
64/// # Limitations
65///
66/// - The macro currently does not support Rust async functions.
67/// - Nested data structures may not be fully supported.
68///
69#[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