use proc_macro2::{TokenStream, Ident, Span};
use quote::quote;
use crate::input::Input;
#[cfg(debug_assertions)]
fn check_collisions(items: &[Ident]) -> TokenStream {
let len = items.len();
quote!(
const __NO_COLLISIONS: () = {
let hashes: [usize; #len] = [#(#items),*];
let mut i: usize = 0;
while i < #len {
let mut j: usize = i + 1;
while j < #len {
if hashes[i] == hashes[j] {
panic!("Collision occured at keys");
}
j += 1;
}
i += 1;
}
};
)
}
pub fn into_stream(input: &Input) -> TokenStream {
let hash = &input.hash_function;
let target = &input.target;
let default = &input.default_arm;
let arms = input.arms.iter();
let arm_keys = arms.clone().map(|x| &x.key);
let arm_exprs = arms.clone().map(|x| &x.value);
let hash_names: Vec<_> = (0usize..arms.len()).map(|x| {
Ident::new(&format!("__HASH{}", x), Span::call_site())
}).collect();
let const_hashes = hash_names.iter()
.zip(arm_keys.clone())
.map(|(name, value)| quote!(const #name: usize = #hash(#value);));
#[cfg(debug_assertions)]
let collision_check = check_collisions(&hash_names);
#[cfg(not(debug_assertions))]
let collision_check = TokenStream::new();
let arms_quoted = hash_names.iter()
.zip(arm_keys)
.zip(arm_exprs)
.map(|((a, b), c)| (a, b, c))
.map(|(hash, key, expr)| quote!(#hash => if v == #key { Some(#expr) } else { None }));
quote!({
#(#const_hashes)*
#collision_check
let v = #target;
let result = match #hash(v) {
#(#arms_quoted,)*
_ => None,
};
match result {
Some(v) => v,
None => #default,
}
})
}