1use 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 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 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}