From 4486f153909667edd1f8cce2e85d1079ab0a4e8e Mon Sep 17 00:00:00 2001 From: Austin Hicks Date: Sat, 4 Nov 2023 13:53:01 -0700 Subject: [PATCH] Macroize ToNamedInputs and ToNamedOutputs --- crates/synthizer/src/nodes/audio_output.rs | 7 +- crates/synthizer/src/nodes/trig.rs | 12 +- crates/synthizer/src/properties.rs | 8 +- .../src/derive_named_inputs_outputs.rs | 126 ++++++++++++++++++ crates/synthizer_macros_internal/src/lib.rs | 13 ++ 5 files changed, 144 insertions(+), 22 deletions(-) create mode 100644 crates/synthizer_macros_internal/src/derive_named_inputs_outputs.rs diff --git a/crates/synthizer/src/nodes/audio_output.rs b/crates/synthizer/src/nodes/audio_output.rs index 20b9bb6..4ef15d5 100644 --- a/crates/synthizer/src/nodes/audio_output.rs +++ b/crates/synthizer/src/nodes/audio_output.rs @@ -11,16 +11,11 @@ use crate::internal_object_handle::InternalObjectHandle; use crate::server::Server; use crate::unique_id::UniqueId; +#[derive(synthizer_macros_internal::ToNamedInputs)] pub(crate) struct Inputs<'a> { input: &'a [AllocatedBlock], } -impl<'a> ToNamedInputs<'a> for Inputs<'a> { - fn to_named_inputs<'b>(inputs: &'b mut InputsByIndex<'a>) -> Self { - Inputs { input: inputs[0] } - } -} - pub(crate) struct AudioOutputNodeAt { format: ChannelFormat, props: (), diff --git a/crates/synthizer/src/nodes/trig.rs b/crates/synthizer/src/nodes/trig.rs index 5fcf6ad..5788206 100644 --- a/crates/synthizer/src/nodes/trig.rs +++ b/crates/synthizer/src/nodes/trig.rs @@ -29,21 +29,11 @@ pub struct TrigWaveformSlots { pub(super) frequency: Slot, } +#[derive(synthizer_macros_internal::ToNamedOutputs)] pub(crate) struct TrigWaveformOutputs<'a> { output: OutputDestination<'a>, } -// TODO: macro this. -impl<'a> ToNamedOutputs<'a> for TrigWaveformOutputs<'a> { - fn to_named_outputs<'b>( - outputs: &'b mut crate::nodes::OutputsByIndex<'a>, - ) -> TrigWaveformOutputs<'a> { - TrigWaveformOutputs { - output: outputs.pop_at(0).unwrap(), - } - } -} - impl HasNodeDescriptor for TrigWaveformNodeAt { type Outputs<'a> = TrigWaveformOutputs<'a>; type Inputs<'a> = (); diff --git a/crates/synthizer/src/properties.rs b/crates/synthizer/src/properties.rs index 969d56b..c6adc02 100644 --- a/crates/synthizer/src/properties.rs +++ b/crates/synthizer/src/properties.rs @@ -199,11 +199,9 @@ impl Slot { /// Try to set this property from a [PropertyValue] and panic if this was not possible. pub(crate) fn set_from_property_value(&mut self, val: PropertyValue, why: ChangeState) { - let Ok(val): Result = val - .try_into() - else { - panic!("Internal error: a property value of the wrong type reached a property slot"); - }; + let Ok(val): Result = val.try_into() else { + panic!("Internal error: a property value of the wrong type reached a property slot"); + }; self.data = val.extract(); self.change_state = why; diff --git a/crates/synthizer_macros_internal/src/derive_named_inputs_outputs.rs b/crates/synthizer_macros_internal/src/derive_named_inputs_outputs.rs new file mode 100644 index 0000000..46e319d --- /dev/null +++ b/crates/synthizer_macros_internal/src/derive_named_inputs_outputs.rs @@ -0,0 +1,126 @@ +//! Implementation of derives for ToNamedInputs and ToNamedOutputs. +//! +//! These share a lot of code because they are identical trait bodies, just with different names for the functions. +use darling::FromDeriveInput; +use proc_macro_error::abort; + +#[derive(FromDeriveInput)] +#[darling(supports(struct_named))] +struct NamedArgs { + ident: syn::Ident, + data: darling::ast::Data<(), syn::Field>, +} + +/// Parameters for performing the generation of whichever case we are generating. +struct Opts { + /// Full path to the trait. + trait_path: syn::Path, + + /// Name of the function. + func_name: syn::Ident, + + /// Name of the argument to the function. + arg_name: syn::Ident, + + /// Type of the argument, pointing at nodes::traits::BYIndex. + arg_ty_path: syn::Path, +} + +/// Punch out either named inputs or outputs. +fn punchout(opts: Opts, tokens: proc_macro2::TokenStream) -> proc_macro2::TokenStream { + let derive_input: syn::DeriveInput = match syn::parse2(tokens) { + Ok(x) => x, + Err(e) => { + let span = e.span(); + abort!(span, e); + } + }; + + let input = match NamedArgs::from_derive_input(&derive_input) { + Ok(x) => x, + Err(e) => { + let span = e.span(); + abort!(span, e); + } + }; + + let Opts { + arg_name, + arg_ty_path, + func_name, + trait_path, + } = opts; + + let struct_ident = input.ident; + + // This is validated by darling already. + let fields = input + .data + .take_struct() + .unwrap() + .fields + .into_iter() + .map(|x| x.ident.unwrap()) + .collect::>(); + if fields.is_empty() { + proc_macro_error::abort_call_site!("This struct must have at least one field. If you are trying to have no inputs or outputs, use `()` here instead."); + } + + // We want to avoid continually shifting vectors forward. To do that, we will reverse the fields and pop from the + // back of the vector. + let mut fields_rev = fields; + fields_rev.reverse(); + + quote::quote!( + impl<'a> #trait_path<'a> for #struct_ident<'a> { + fn #func_name<'b>(#arg_name: &'b mut #arg_ty_path<'a>) -> #struct_ident<'a> { + #struct_ident { + #( + #fields_rev: #arg_name + .pop() + .expect("Mismatch between the node descriptor and the number of fields in this struct") + ),* + } + } + } + ) +} + +fn ident_cs(name: &str) -> syn::Ident { + syn::Ident::new(name, proc_macro2::Span::call_site()) +} + +fn path_cs(path: &str) -> syn::Path { + use std::str::FromStr; + syn::parse2(proc_macro2::TokenStream::from_str(path).unwrap()).unwrap() +} + +pub(crate) fn derive_to_named_outputs_impl( + tokens: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + punchout( + Opts { + arg_name: ident_cs("outputs"), + arg_ty_path: path_cs("crate::nodes::OutputsByIndex"), + func_name: ident_cs("to_named_outputs"), + trait_path: path_cs("crate::nodes::traits::ToNamedOutputs"), + }, + tokens.into(), + ) + .into() +} + +pub(crate) fn derive_to_named_inputs_impl( + tokens: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + punchout( + Opts { + arg_name: ident_cs("inputs"), + arg_ty_path: path_cs("crate::nodes::InputsByIndex"), + func_name: ident_cs("to_named_inputs"), + trait_path: path_cs("crate::nodes::traits::ToNamedInputs"), + }, + tokens.into(), + ) + .into() +} diff --git a/crates/synthizer_macros_internal/src/lib.rs b/crates/synthizer_macros_internal/src/lib.rs index 0d5332e..e19771f 100644 --- a/crates/synthizer_macros_internal/src/lib.rs +++ b/crates/synthizer_macros_internal/src/lib.rs @@ -8,6 +8,7 @@ //! //! This crate contains dummy forwarders at the root to let us split it into files. See the individual modules for docs //! on the macro. Procmacro limitations currently require that they be at the root. +mod derive_named_inputs_outputs; mod property_slots_impl; mod utils; @@ -19,3 +20,15 @@ pub fn property_slots( ) -> proc_macro::TokenStream { property_slots_impl::property_slots_impl(attrs.into(), body.into()).into() } + +#[proc_macro_derive(ToNamedInputs)] +#[proc_macro_error::proc_macro_error] +pub fn to_named_inputs(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream { + crate::derive_named_inputs_outputs::derive_to_named_inputs_impl(tokens) +} + +#[proc_macro_derive(ToNamedOutputs)] +#[proc_macro_error::proc_macro_error] +pub fn to_named_outputs(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream { + crate::derive_named_inputs_outputs::derive_to_named_outputs_impl(tokens) +}