Skip to content

Commit 9556d8d

Browse files
committed
new & improved Control macro is back
1 parent d31cf01 commit 9556d8d

File tree

2 files changed

+280
-1
lines changed

2 files changed

+280
-1
lines changed

proc-macros/src/control.rs

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
use convert_case::{Case, Casing};
2+
use proc_macro::TokenStream;
3+
use quote::{format_ident, quote};
4+
use std::collections::HashSet;
5+
use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Fields, Ident};
6+
7+
use crate::core_crate_name;
8+
9+
// TODO: see
10+
// https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=03943d1dfbf41bd63878bfccb1c64670
11+
// for an intriguing bit of code. Came from
12+
// https://users.rust-lang.org/t/is-implementing-a-derive-macro-for-converting-nested-structs-to-flat-structs-possible/65839/3
13+
14+
pub(crate) fn impl_control_derive(input: TokenStream, primitives: &HashSet<Ident>) -> TokenStream {
15+
TokenStream::from({
16+
let input = parse_macro_input!(input as DeriveInput);
17+
let generics = &input.generics;
18+
let data = &input.data;
19+
let struct_name = &input.ident;
20+
let (_impl_generics, ty_generics, _where_clause) = generics.split_for_impl();
21+
let core_crate = format_ident!("{}", core_crate_name());
22+
23+
// Code adapted from https://blog.turbo.fish/proc-macro-error-handling/
24+
// Thank you!
25+
let fields = match data {
26+
Data::Struct(DataStruct {
27+
fields: Fields::Named(fields),
28+
..
29+
}) => &fields.named,
30+
_ => panic!("this derive macro works only on structs with named fields"),
31+
};
32+
let attr_fields = fields.into_iter().fold(Vec::default(), |mut v, f| {
33+
let attrs: Vec<_> = f
34+
.attrs
35+
.iter()
36+
.filter(|attr| attr.path.is_ident("control"))
37+
.collect();
38+
if !attrs.is_empty() {
39+
match &f.ty {
40+
syn::Type::Path(t) => {
41+
if let Some(ident) = t.path.get_ident() {
42+
v.push((f.ident.as_ref().unwrap().clone(), ident.clone()));
43+
}
44+
}
45+
_ => todo!(),
46+
}
47+
}
48+
v
49+
});
50+
51+
// FOO_SIZE = 3; self.foo contains 3 controlled fields
52+
fn size_const_id(ident: &Ident) -> Ident {
53+
format_ident!("{}_SIZE", ident.to_string().to_case(Case::UpperSnake))
54+
}
55+
// FOO_NAME = "foo"; self.foo is addressable by "foo"
56+
fn name_const_id(ident: &Ident) -> Ident {
57+
let ident_upper = ident.to_string().to_case(Case::UpperSnake);
58+
format_ident!("{}_NAME", ident_upper)
59+
}
60+
// FOO_INDEX = 5; address the foo element with index 5 (and maybe higher if node)
61+
fn index_const_id(ident: &Ident) -> Ident {
62+
let ident_upper = ident.to_string().to_case(Case::UpperSnake);
63+
format_ident!("{}_INDEX", ident_upper)
64+
}
65+
// FOO_RANGE_END = 7; address foo's flattened elements with indexes 5, 6, and 7.
66+
fn index_range_end_const_id(ident: &Ident) -> Ident {
67+
let ident_upper = ident.to_string().to_case(Case::UpperSnake);
68+
format_ident!("{}_RANGE_END", ident_upper)
69+
}
70+
71+
let mut size_const_ids = Vec::default();
72+
let mut size_const_values = Vec::default();
73+
attr_fields.iter().for_each(|(ident, ident_type)| {
74+
let size_const_name = size_const_id(ident);
75+
size_const_ids.push(size_const_name.clone());
76+
77+
if primitives.contains(ident_type) {
78+
size_const_values.push(quote! { 1 });
79+
} else {
80+
size_const_values.push(quote! { #ident_type::STRUCT_SIZE });
81+
}
82+
});
83+
let size_const_body = quote! {
84+
#( const #size_const_ids: usize = #size_const_values; )*
85+
};
86+
87+
let mut index_const_ids = Vec::default();
88+
let mut index_const_range_end_ids = Vec::default();
89+
let mut index_const_values = Vec::default();
90+
91+
// This loop calculates each field's index.
92+
//
93+
// Since proc macros don't have access to any other information than the
94+
// struct TokenStream, we can't incorporate any sub-structure
95+
// information (such as how big the field is) except by referring to
96+
// consts. In other words, if Struct contains EmbeddedStruct, we can't
97+
// ask how big EmbeddedStruct is, but we can refer to
98+
// EmbeddedStruct::STRUCT_SIZE and let the compiler figure out that
99+
// value during the build.
100+
//
101+
// Thus, a field's index will always be either (1) zero if it's the
102+
// first, or (2) the index of the prior field + the size of the prior
103+
// field. So we need to keep track of the prior field name, which
104+
// enables us to build up the current value from the prior one.
105+
let mut prior_ident: Option<&Ident> = None;
106+
attr_fields.iter().for_each(|(ident, _)| {
107+
index_const_ids.push(index_const_id(ident));
108+
index_const_range_end_ids.push(index_range_end_const_id(ident));
109+
if let Some(prior) = prior_ident {
110+
let prior_index_const_name = index_const_id(prior);
111+
let prior_size_const_name = size_const_id(prior);
112+
index_const_values
113+
.push(quote! { Self::#prior_index_const_name + Self::#prior_size_const_name });
114+
} else {
115+
index_const_values.push(quote! { 0 });
116+
}
117+
prior_ident = Some(ident);
118+
});
119+
let mut name_const_ids = Vec::default();
120+
let mut name_const_values = Vec::default();
121+
attr_fields.iter().for_each(|(ident, _)| {
122+
let name_const = name_const_id(ident);
123+
name_const_ids.push(name_const.clone());
124+
name_const_values.push(ident.to_string().to_case(Case::Kebab));
125+
});
126+
127+
let main_const_body = quote! {
128+
#( pub const #index_const_ids: usize = #index_const_values; )*
129+
#( pub const #name_const_ids: &str = #name_const_values; )*
130+
};
131+
let range_const_body = quote! {
132+
#( pub const #index_const_range_end_ids: usize = #index_const_values + #size_const_values - 1; )*
133+
};
134+
let struct_size_const_body = quote! {
135+
pub const STRUCT_SIZE: usize = 0 + #( Self::#size_const_ids )+* ;
136+
};
137+
138+
let mut id_bodies = Vec::default();
139+
let mut setter_bodies = Vec::default();
140+
attr_fields.iter().for_each(|(ident, ident_type)| {
141+
let id = ident.to_string().to_case(Case::Kebab);
142+
if primitives.contains(ident_type) {
143+
let name_const = format_ident!("set_{}", ident);
144+
id_bodies.push(quote! {Some(#id.to_string())});
145+
setter_bodies.push(quote! {self.#name_const(value.into());});
146+
} else {
147+
let field_index_name = index_const_id(ident);
148+
let name_const = name_const_id(ident);
149+
id_bodies.push(quote! { Some(format!("{}-{}", Self::#name_const, self.#ident.control_name_for_index(index - Self::#field_index_name).unwrap()))});
150+
setter_bodies
151+
.push(quote! {self.#ident.control_set_param_by_index(index - Self::#field_index_name, value);});
152+
}
153+
});
154+
let control_name_for_index_body = quote! {
155+
fn control_name_for_index(&self, index: usize) -> Option<String> {
156+
match index {
157+
#( Self::#index_const_ids..=Self::#index_const_range_end_ids => {#id_bodies} ),*
158+
_ => {None},
159+
}
160+
}
161+
};
162+
let control_set_param_by_index_bodies = quote! {
163+
fn control_set_param_by_index(&mut self, index: usize, value: #core_crate::control::F32ControlValue) {
164+
match index {
165+
#( Self::#index_const_ids..=Self::#index_const_range_end_ids => {#setter_bodies} ),*
166+
_ => {},
167+
}
168+
}
169+
};
170+
171+
// These need to be separate vecs because we divide the fields into
172+
// groups of maybe different sizes, which is a repetitions no-no.
173+
let mut leaf_names = Vec::default();
174+
let mut leaf_indexes = Vec::default();
175+
let mut node_names = Vec::default();
176+
let mut node_indexes = Vec::default();
177+
let mut node_fields = Vec::default();
178+
let mut node_field_lens = Vec::default();
179+
attr_fields.iter().for_each(|(ident, ident_type)| {
180+
let const_name = name_const_id(ident);
181+
let field_index_name = index_const_id(ident);
182+
if primitives.contains(ident_type) {
183+
leaf_names.push(quote! { Self::#const_name });
184+
leaf_indexes.push(quote! { Self::#field_index_name });
185+
} else {
186+
node_names.push(quote! { Self::#const_name });
187+
node_indexes.push(quote! { Self::#field_index_name });
188+
node_fields.push(ident);
189+
// Includes the dash at the end that separates the field parts in the ID
190+
let node_field_len = ident.to_string().len() + 1;
191+
node_field_lens.push(quote! {#node_field_len});
192+
}
193+
});
194+
let control_index_for_name_body = quote! {
195+
fn control_index_for_name(&self, name: &str) -> Option<usize> {
196+
match name {
197+
#( #leaf_names => Some(#leaf_indexes), )*
198+
_ => {
199+
#(
200+
if name.starts_with(#node_names) {
201+
if let Some(r) = self.#node_fields.control_index_for_name(&name[#node_field_lens..]) {
202+
return Some(r + #node_indexes)
203+
}
204+
}
205+
)*
206+
None
207+
},
208+
}
209+
}
210+
};
211+
212+
let quote = quote! {
213+
#[automatically_derived]
214+
impl #generics #struct_name #ty_generics {
215+
#size_const_body
216+
#main_const_body
217+
#range_const_body
218+
#struct_size_const_body
219+
}
220+
#[automatically_derived]
221+
impl #generics #core_crate::traits::Controllable for #struct_name #ty_generics {
222+
fn control_index_count(&self) -> usize { Self::STRUCT_SIZE }
223+
fn control_set_param_by_name(&mut self, name: &str, value: #core_crate::control::F32ControlValue) {
224+
if let Some(index) = self.control_index_for_name(name) {
225+
self.control_set_param_by_index(index, value);
226+
} else {
227+
eprintln!("Warning: couldn't set param named '{}'", name);
228+
}
229+
}
230+
#control_name_for_index_body
231+
#control_index_for_name_body
232+
#control_set_param_by_index_bodies
233+
}
234+
};
235+
quote
236+
})
237+
}

proc-macros/src/lib.rs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@
22

33
//! This crate provides macros that make Entity development easier.
44
5+
use control::impl_control_derive;
56
use everything::parse_and_generate_everything;
67
use nano::impl_nano_derive;
78
use proc_macro::TokenStream;
89
use proc_macro_crate::crate_name;
910
use quote::{format_ident, quote};
10-
use syn::{parse_macro_input, DeriveInput};
11+
use std::collections::HashSet;
12+
use syn::{parse_macro_input, DeriveInput, Ident};
1113
use uid::impl_uid_derive;
1214
use views::parse_and_generate_views;
1315

16+
mod control;
1417
mod everything;
1518
mod nano;
1619
mod uid;
@@ -49,6 +52,45 @@ pub fn derive_views(input: TokenStream) -> TokenStream {
4952
))
5053
}
5154

55+
/// field types that don't recurse further for #[derive(Control)] purposes
56+
fn make_primitives() -> HashSet<Ident> {
57+
vec![
58+
"BipolarNormal",
59+
"FrequencyHz",
60+
"Normal",
61+
"ParameterType",
62+
"Ratio",
63+
"String",
64+
"Waveform",
65+
"bool",
66+
"char",
67+
"f32",
68+
"f64",
69+
"i128",
70+
"i16",
71+
"i32",
72+
"i64",
73+
"i8",
74+
"u128",
75+
"u16",
76+
"u32",
77+
"u64",
78+
"u8",
79+
"usize",
80+
]
81+
.into_iter()
82+
.fold(HashSet::default(), |mut hs, e| {
83+
hs.insert(format_ident!("{}", e));
84+
hs
85+
})
86+
}
87+
88+
/// The [Control] macro derives the code that allows automation (one entity's output driving another entity's control).
89+
#[proc_macro_derive(Control, attributes(control))]
90+
pub fn derive_control(input: TokenStream) -> TokenStream {
91+
impl_control_derive(input, &make_primitives())
92+
}
93+
5294
// Some of the code generated in these macros uses the groove-core crate, but
5395
// groove-core also uses this proc-macro lib. So we need to correct the
5496
// reference to groove-core to sometimes be just `crate`.

0 commit comments

Comments
 (0)