use std::path::PathBuf;
const LETTERS: [char; 9] = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'];
// Enums will have 2^N fields, so we default to a small number and expose feature flags to generate
// larger `DeltaN` types.
//
// TODO: Increase to 4 whenever we're working on supporting 3-tuples and 4-tuples
//
// TODO: Expose those feature flags. Such as `delta-5` `delta-6` etc. Experiment to see how large
// we can go without impacting compile times too much.
const MAX_DELTA_N: u8 = 2;
fn main() {
let delta_n_types = generate_delta_n_types();
let out_dir = std::env::var("OUT_DIR").unwrap();
std::fs::write(
PathBuf::from(out_dir).join("delta_n_types.rs"),
delta_n_types,
)
.unwrap();
}
/// Generate `DeltaN` types.
///
/// ```no_run
/// #[derive(serde::Serialize)]
/// #[cfg_attr(feature = "impl-tester", derive(Debug, PartialEq))]
/// #[allow(non_camel_case_types, missing_docs)]
/// pub enum Delta2 {
/// NoChange,
/// Change_0(A),
/// Change_1(B),
/// Change_0_1(A, B),
/// }
///
/// #[derive(serde::Deserialize)]
/// #[allow(non_camel_case_types, missing_docs)]
/// pub enum DiffOwned2 {
/// NoChange,
/// Change_0(A),
/// Change_1(B),
/// Change_0_1(A, B),
/// }
/// ```
fn generate_delta_n_types() -> String {
let mut all_types = "".to_string();
for field_count in 2..=MAX_DELTA_N {
let bool_combinations = make_bool_combinations(field_count as _);
let mut change_combinations = "".to_string();
let diff_n_generics: String = LETTERS[0..field_count as usize]
.iter()
.map(|l| format!("{},", l))
.collect();
for bools in bool_combinations {
let mut change_str = "Change".to_string();
let mut changed_generics = "".to_string();
bools
.iter()
.enumerate()
.filter(|(_, changed)| **changed)
.for_each(|(idx, _)| {
change_str += &format!("_{}", idx);
changed_generics += &format!("{}, ", LETTERS[idx]);
});
if &change_str == "Change" {
continue;
}
change_combinations += &format!(
r#"{change_str}({changed_generics}),
"#,
change_str = change_str,
changed_generics = changed_generics
);
}
let diff_n = format!(
r#"
#[derive(serde::Serialize)]
#[cfg_attr(any(test, feature = "impl-tester"), derive(Debug, PartialEq))]
#[allow(non_camel_case_types, missing_docs)]
pub enum Delta{field_count}<{diff_n_generics}> {{
NoChange,
{change_combinations}
}}"#,
field_count = field_count,
change_combinations = change_combinations,
diff_n_generics = diff_n_generics,
);
let diff_n_owned = format!(
r#"
#[derive(serde::Deserialize)]
#[allow(non_camel_case_types, missing_docs)]
pub enum DeltaOwned{field_count}<{diff_n_generics}> {{
NoChange,
{change_combinations}
}}"#,
field_count = field_count,
change_combinations = change_combinations,
diff_n_generics = diff_n_generics,
);
all_types += &diff_n;
all_types += &diff_n_owned;
}
all_types
}
/// Every possible combination of `n` booleans being true or false
/// There are `2 ^ field_count` combinations.
///
/// So for two fields the four combinations are:
///
/// [false, false], [true, false], [false, true], [true, true]
fn make_bool_combinations(field_count: usize) -> Vec> {
let mut all = vec![];
let start = vec![false; field_count];
bool_combinations_recursive(&mut all, 0, field_count, start);
all
}
fn bool_combinations_recursive(
all: &mut Vec>,
start_idx: usize,
field_count: usize,
current: Vec,
) {
if start_idx > field_count {
return;
}
all.push(current.clone());
for idx in start_idx..field_count {
let mut flipped = current.clone();
flipped[idx] = !flipped[idx];
bool_combinations_recursive(all, idx + 1, field_count, flipped);
}
}