1extern crate proc_macro;
2
3mod machine_parameters;
4mod parameter_range;
5mod util;
6
7use machine_parameters::*;
8use parameter_range::*;
9use proc_macro::TokenStream;
10use quote::{format_ident, quote};
11use syn::{
12 parse_macro_input, punctuated::Punctuated, token::Comma, DeriveInput, Error as SynError, ItemFn,
13};
14
15#[proc_macro_attribute]
28pub fn parameter_range(args: TokenStream, input: TokenStream) -> TokenStream {
29 let input_fn = parse_macro_input!(input as ItemFn);
30
31 let args: Punctuated<RangeArg, Comma> =
32 parse_macro_input!(args with Punctuated::parse_terminated);
33
34 let mut checks = Vec::new();
35 for arg in args {
36 let param_name_ident = format_ident!("{}", arg.param_name);
37 let is_inclusive = arg.range_expr.contains("..=");
38 let range_parts: Vec<&str> = if is_inclusive {
39 arg.range_expr.split("..=").collect()
40 } else {
41 arg.range_expr.split("..").collect()
42 };
43
44 if range_parts.len() != 2 {
45 return SynError::new_spanned(
46 ¶m_name_ident,
47 "Invalid range expression format. Expected format: `start..=end` or `start..end`",
48 )
49 .to_compile_error()
50 .into();
51 }
52
53 let start = range_parts[0];
54 let end = range_parts[1];
55
56 let type_annotation = arg.param_type.map_or_else(
57 || quote! { _ },
58 |ty| {
59 let ty = syn::parse_str::<syn::Type>(&ty).expect("Invalid type annotation");
60 quote! { #ty }
61 },
62 );
63
64 let range_check = if is_inclusive {
65 if arg.is_optional {
66 quote! {
67 let ___start: #type_annotation = #start.parse().expect("Invalid range start");
68 let ___end: #type_annotation = #end.parse().expect("Invalid range end");
69
70
71 if let Some(false) = #param_name_ident.as_ref().map(|&x| x >= ___start && x <= ___end) {
72 return Err(RytmError::Parameter(ParameterError::Range {
73 value: #param_name_ident.unwrap().to_string(),
74 parameter_name: stringify!(#param_name_ident).to_string(),
75 }));
76 }
77 }
78 } else {
79 quote! {
80 let ___start: #type_annotation = #start.parse().expect("Invalid range start");
81 let ___end: #type_annotation = #end.parse().expect("Invalid range end");
82
83 if !(___start..=___end).contains(&#param_name_ident) {
84 return Err(RytmError::Parameter(ParameterError::Range {
85 value: #param_name_ident.to_string(),
86 parameter_name: stringify!(#param_name_ident).to_string(),
87 }));
88 }
89 }
90 }
91 } else if arg.is_optional {
92 quote! {
93 let ___start: #type_annotation = #start.parse().expect("Invalid range start");
94 let ___end: #type_annotation = #end.parse().expect("Invalid range end");
95
96
97 if let Some(false) = #param_name_ident.as_ref().map(|&x| x >= ___start && x < ___end) {
98 return Err(RytmError::Parameter(ParameterError::Range {
99 value: #param_name_ident.unwrap().to_string(),
100 parameter_name: stringify!(#param_name_ident).to_string(),
101 }));
102 }
103 }
104 } else {
105 quote! {
106 let ___start: #type_annotation = #start.parse().expect("Invalid range start");
107 let ___end: #type_annotation = #end.parse().expect("Invalid range end");
108
109 if !(___start..___end).contains(&#param_name_ident) {
110 return Err(RytmError::Parameter(ParameterError::Range {
111 value: #param_name_ident.to_string(),
112 parameter_name: stringify!(#param_name_ident).to_string(),
113 }));
114 }
115 }
116 };
117
118 checks.push(range_check);
119 }
120
121 let ItemFn {
122 attrs,
123 vis,
124 sig,
125 block,
126 } = input_fn;
127
128 let result = quote! {
129 #(#attrs)* #vis #sig {
130 #(#checks)*
131 #block
132 }
133 };
134
135 result.into()
136}
137
138#[proc_macro_attribute]
161pub fn machine_parameters(args: TokenStream, input: TokenStream) -> TokenStream {
162 let args = parse_macro_input!(args as ParameterArgs);
163 let input_struct = parse_macro_input!(input as DeriveInput);
164 let struct_name = input_struct.ident.clone();
165
166 let methods = args.0.iter().map(|arg| {
167 let setter_and_plock_methods =
169 generate_setter_and_plock_methods_with_range_check(arg, &struct_name);
170 let getter = generate_getter(arg, &struct_name);
171 quote! { #setter_and_plock_methods #getter }
172 });
173
174 let apply_to_raw_sound_values_inner =
175 args.0.iter().map(generate_apply_to_raw_sound_values_inner);
176
177 let result = quote! {
178 #input_struct
179
180 impl #struct_name {
181 pub(crate) fn apply_to_raw_sound_values(&self, raw_sound: &mut ar_sound_t) {
182 #(#apply_to_raw_sound_values_inner)*
183 }
184 }
185
186 #(#methods)*
187 };
188
189 result.into()
190}