Skip to main content

aether_ndk_macro/
lib.rs

1//! Proc-macro crate for the Aether Node Development Kit.
2//! Uses syn v2 API.
3
4use proc_macro::TokenStream;
5use quote::quote;
6use syn::{
7    parse_macro_input, Data, DeriveInput, Expr, Fields, Lit,
8};
9
10#[proc_macro_attribute]
11pub fn aether_node(_attr: TokenStream, item: TokenStream) -> TokenStream {
12    let input = parse_macro_input!(item as DeriveInput);
13    let name = &input.ident;
14    let name_str = name.to_string();
15
16    let fields = match &input.data {
17        Data::Struct(s) => match &s.fields {
18            Fields::Named(f) => &f.named,
19            _ => panic!("#[aether_node] requires named fields"),
20        },
21        _ => panic!("#[aether_node] can only be applied to structs"),
22    };
23
24    let mut param_defs: Vec<proc_macro2::TokenStream> = Vec::new();
25    let mut param_defaults: Vec<proc_macro2::TokenStream> = Vec::new();
26    let mut state_defaults: Vec<proc_macro2::TokenStream> = Vec::new();
27
28    for field in fields {
29        let field_name = field.ident.as_ref().unwrap();
30        let mut is_param = false;
31        let mut param_name = field_name.to_string();
32        let mut min = 0.0f32;
33        let mut max = 1.0f32;
34        let mut default = 0.0f32;
35
36        for attr in &field.attrs {
37            if attr.path().is_ident("param") {
38                is_param = true;
39                // Parse key=value pairs inside #[param(...)]
40                let _ = attr.parse_nested_meta(|meta| {
41                    let key = meta.path.get_ident()
42                        .map(|i| i.to_string())
43                        .unwrap_or_default();
44                    let value: Expr = meta.value()?.parse()?;
45                    match key.as_str() {
46                        "name" => {
47                            if let Expr::Lit(el) = &value {
48                                if let Lit::Str(s) = &el.lit {
49                                    param_name = s.value();
50                                }
51                            }
52                        }
53                        "min" => {
54                            if let Expr::Lit(el) = &value {
55                                if let Lit::Float(f) = &el.lit {
56                                    min = f.base10_parse().unwrap_or(0.0);
57                                }
58                            }
59                        }
60                        "max" => {
61                            if let Expr::Lit(el) = &value {
62                                if let Lit::Float(f) = &el.lit {
63                                    max = f.base10_parse().unwrap_or(1.0);
64                                }
65                            }
66                        }
67                        "default" => {
68                            if let Expr::Lit(el) = &value {
69                                if let Lit::Float(f) = &el.lit {
70                                    default = f.base10_parse().unwrap_or(0.0);
71                                }
72                            }
73                        }
74                        _ => {}
75                    }
76                    Ok(())
77                });
78            }
79        }
80
81        if is_param {
82            param_defs.push(quote! {
83                ::aether_ndk::ParamDef {
84                    name: #param_name,
85                    min: #min,
86                    max: #max,
87                    default: #default,
88                }
89            });
90            param_defaults.push(quote! { #field_name: #default });
91        } else {
92            state_defaults.push(quote! { #field_name: Default::default() });
93        }
94    }
95
96    let param_count = param_defs.len();
97    let all_defaults: Vec<_> = param_defaults.iter().chain(state_defaults.iter()).collect();
98
99    // Strip #[param] attrs from the struct
100    let mut clean_input = input.clone();
101    if let Data::Struct(ref mut s) = clean_input.data {
102        if let Fields::Named(ref mut f) = s.fields {
103            for field in f.named.iter_mut() {
104                field.attrs.retain(|a| !a.path().is_ident("param"));
105            }
106        }
107    }
108
109    let expanded = quote! {
110        #clean_input
111
112        impl #name {
113            pub const PARAM_COUNT: usize = #param_count;
114
115            pub fn param_defs() -> &'static [::aether_ndk::ParamDef] {
116                static DEFS: &[::aether_ndk::ParamDef] = &[#(#param_defs),*];
117                DEFS
118            }
119        }
120
121        impl Default for #name {
122            fn default() -> Self {
123                Self { #(#all_defaults),* }
124            }
125        }
126
127        impl ::aether_ndk::AetherNodeMeta for #name {
128            fn type_name() -> &'static str { #name_str }
129            fn param_defs() -> &'static [::aether_ndk::ParamDef] {
130                #name::param_defs()
131            }
132        }
133    };
134
135    TokenStream::from(expanded)
136}