xtal-macros 0.1.0

Macros for the the Xtal creative coding framework
Documentation
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{
    parse::{Parse, ParseStream},
    parse_macro_input, DeriveInput, Ident, LitInt, Token,
};

pub fn uniforms_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
    let input = parse_macro_input!(item as DeriveInput);
    let attrs = &input.attrs;
    let vis = &input.vis;
    let args = parse_macro_input!(attr as UniformsArgs);
    let banks = args.banks;
    let field_names = generate_field_names(banks);
    let struct_name = &input.ident;

    let first_bank = field_names.first().cloned();
    let remaining_banks =
        field_names.iter().skip(1).cloned().collect::<Vec<_>>();

    let set_match_arms = field_names.iter().map(|field_name| {
        quote! {
            stringify!(#field_name) => {
                let idx = bank
                    .chars()
                    .nth(1)
                    .unwrap_or('1')
                    .to_digit(10)
                    .unwrap_or(1) as usize - 1;

                if idx < 4 {
                    self.#field_name[idx] = value;
                }
            }
        }
    });

    let expanded_struct = quote! {
        #[repr(C)]
        #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
        #(#attrs)*
        #vis struct #struct_name {
            #(#field_names: [f32; 4],)*
        }

        impl Default for #struct_name {
            fn default() -> Self {
                Self {
                    #(#field_names: [0.0; 4],)*
                }
            }
        }

        impl #struct_name {
            pub fn from_hub<T: TimingSource>(hub: &ControlHub<T>) -> Self {
                Self {
                    #(#field_names: [
                        hub.get(&format!("{}{}", stringify!(#field_names), 1)),
                        hub.get(&format!("{}{}", stringify!(#field_names), 2)),
                        hub.get(&format!("{}{}", stringify!(#field_names), 3)),
                        hub.get(&format!("{}{}", stringify!(#field_names), 4)),
                    ],)*
                }
            }

            pub fn set(&mut self, bank: &str, value: f32) {
                if bank.len() < 2 {
                    return;
                }

                let field = &bank[..1];

                match field {
                    #(#set_match_arms)*
                    _ => {}
                }
            }
        }

        impl<T: TimingSource> From<(&WindowRect, &ControlHub<T>)> for #struct_name {
            fn from((window_rect, hub): (&WindowRect, &ControlHub<T>)) -> Self {
                Self {
                    #first_bank: [
                        window_rect.w(),
                        window_rect.h(),
                        hub.get(&format!("{}{}", stringify!(#first_bank), 3)),
                        hub.get(&format!("{}{}", stringify!(#first_bank), 4)),
                    ],
                    #(#remaining_banks: [
                        hub.get(&format!("{}{}", stringify!(#remaining_banks), 1)),
                        hub.get(&format!("{}{}", stringify!(#remaining_banks), 2)),
                        hub.get(&format!("{}{}", stringify!(#remaining_banks), 3)),
                        hub.get(&format!("{}{}", stringify!(#remaining_banks), 4)),
                    ],)*
                }
            }
        }
    };

    expanded_struct.into()
}

struct UniformsArgs {
    banks: usize,
}

impl Parse for UniformsArgs {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let mut banks = 4;

        if !input.is_empty() {
            let name: Ident = input.parse()?;
            if name != "banks" {
                return Err(syn::Error::new(
                    name.span(),
                    "Expected `banks` parameter",
                ));
            }

            input.parse::<Token![=]>()?;
            let value: LitInt = input.parse()?;
            banks = value.base10_parse()?;
        }

        Ok(UniformsArgs { banks })
    }
}

fn generate_field_names(count: usize) -> Vec<syn::Ident> {
    (0..count)
        .map(|i| {
            let c = (b'a' + i as u8) as char;
            format_ident!("{}", c)
        })
        .collect()
}