fib-o1 0.1.3

generate Fibonacci sequence numbers in O(1) time
Documentation
use num::bigint::{BigInt, BigUint};
use std::{fmt::Debug, fmt::Display, fs::File, io::Write, path::Path};

macro_rules! impl_fib {
    ($file:ident, $input_ty:ty, $output_ty:ty, $limit:expr) => {
        implement_fib_for_type::<$input_ty, $output_ty>(&mut $file, stringify!($input_ty), stringify!($output_ty), $limit)?;
    };
    ($file:ident, $input_ty:ty, $output_ty:ty) => {
        impl_fib!($file, $input_ty, $output_ty, <$input_ty>::MAX.try_into().unwrap());
    };
    ( $file:ident, $($input_ty:ty),+ => $output_ty:ty) => {
        $(
            impl_fib!($file, $input_ty, $output_ty);
        )+
    };
    ( $file:ident, $($input_ty:ty),+ => $output_ty:ty, $limit:expr) => {
        $(
            impl_fib!($file, $input_ty, $output_ty, $limit);
        )+
    };
}

macro_rules! limit {
    ($limiter:expr, $input_ty:expr, $limit:expr) => {
        match $input_ty {
            "u8" => $limiter <= u8::MAX as u128 && $limiter < $limit,
            "u16" => $limiter <= u16::MAX as u128 && $limiter < $limit,
            "u32" => $limiter <= u32::MAX as u128 && $limiter < $limit,
            "u64" => $limiter <= u64::MAX as u128 && $limiter < $limit,
            "u128" => $limiter <= u128::MAX as u128 && $limiter < $limit,
            "usize" => $limiter <= usize::MAX as u128 && $limiter < $limit,
            _ => panic!("Unsupported type: {}", $input_ty),
        }
    };
}

fn main() -> std::io::Result<()> {
    #[cfg(feature = "codegen-inplace")]
    let out_dir = "./src";
    #[cfg(not(feature = "codegen-inplace"))]
    let out_dir = std::env::var_os("OUT_DIR").unwrap();
    let out_path = Path::new(&out_dir).join("fib.rs");
    let mut file = File::create(out_path)?;

    // define max(n) for fib(n)
    let mut max_input_exp = 12;
    let feats = vec![
        ("CARGO_FEATURE_POW10", 10),
        ("CARGO_FEATURE_POW11", 11),
        ("CARGO_FEATURE_POW12", 12),
        ("CARGO_FEATURE_POW13", 13),
        ("CARGO_FEATURE_POW14", 14),
    ];
    for feat in feats {
        if std::env::var_os(feat.0).is_some() {
            max_input_exp = feat.1
        }
    }
    let max_input = 2u128.pow(max_input_exp);

    #[cfg(feature = "codegen-inplace")]
    file.write_all(b"pub use crate::Fib;\n")?;

    #[cfg(feature = "bigint")]
    file.write_all(b"use num_bigint::{BigInt,BigUint,ToBigUint,ToBigInt};\n")?;

    file.write_all(format!("pub const MAX_INPUT: u128 = {};", max_input).as_bytes())?;

    impl_fib!(file, u8, u16, u32, u64, u128, usize => u8);
    impl_fib!(file, u8, u16, u32, u64, u128, usize => u16);
    impl_fib!(file, u8, u16, u32, u64, u128, usize => u32);
    impl_fib!(file, u8, u16, u32, u64, u128, usize => u64);
    impl_fib!(file, u8, u16, u32, u64, u128, usize => u128);
    impl_fib!(file, u8, u16, u32, u64, u128, usize => usize);

    #[cfg(feature = "bigint")]
    {
        let big_int_limit = max_input + 1;
        impl_fib!(file, u8, u16, u32, u64, u128, usize => BigInt, big_int_limit.try_into().unwrap());
        impl_fib!(file, u8, u16, u32, u64, u128, usize => BigUint, big_int_limit.try_into().unwrap());
    }

    println!("cargo::rerun-if-changed=build.rs");

    Ok(())
}

fn implement_fib_for_type<I, O>(
    file: &mut File,
    input_ty: &str,
    output_ty: &str,
    limit: u128,
) -> Result<(), std::io::Error>
where
    I: TryFrom<u16>
        + num::CheckedAdd
        + Display
        + std::ops::AddAssign<I>
        + Clone
        + std::cmp::PartialOrd,
    <I as TryFrom<u16>>::Error: Debug,
    O: TryFrom<u64>
        + num::CheckedAdd
        + Display
        + std::ops::AddAssign<O>
        + Clone
        + std::cmp::PartialOrd,
    <O as TryFrom<u64>>::Error: Debug,
{
    let result0 = get_result_ok_internal(&0u64, &0u64, output_ty);
    let result1 = get_result_ok_internal(&0u64, &1u64, output_ty);
    file.write_all(
        format!(
            "impl Fib<{input_ty}, {output_ty}> for {output_ty} {{
    fn fib(n: {input_ty}) -> Result<{output_ty}, crate::OutOfBoundsError<{input_ty}>> {{
        #[allow(unreachable_patterns)]
        match n {{
            0 => Ok({result0}),
            1 => Ok({result1}),\n"
        )
        .as_bytes(),
    )?;
    let mut a = O::try_from(0).unwrap();
    let mut b = O::try_from(1).unwrap();
    let mut limiter = 2;
    while limit!(limiter, input_ty, limit) && a.checked_add(&b).is_some() {
        let result = get_result_ok_internal(&a, &b, output_ty);
        file.write_all(format!("\t\t\t{limiter} => Ok({result}),\n",).as_bytes())?;
        let c = b.clone();
        b = a + b;
        a = c.clone();
        limiter += 1;
    }
    file.write_all(
        b"\t\t\t_ => Err(crate::OutOfBoundsError::from(n)),
        }
    }
}\n\n",
    )?;

    Ok(())
}

fn get_result_ok_internal<I>(a: &I, b: &I, output_ty: &str) -> String
where
    I: num::CheckedAdd
        + Display
        + std::ops::AddAssign<I>
        + Clone
        + std::cmp::PartialOrd
        + TryFrom<u64>,
    <I as TryFrom<u64>>::Error: Debug,
{
    match output_ty {
        "BigInt" => {
            if a.clone() + b.clone()
                > I::try_from(u64::MAX).expect(&format!(
                    "expected <I> big enough to house u64::MAX, found: {}",
                    std::any::type_name::<I>()
                ))
            {
                format!(
                    "BigInt::parse_bytes(b\"{}\", 10).unwrap()",
                    a.clone() + b.clone()
                )
            } else {
                format!("{}u128.to_bigint().unwrap()", a.clone() + b.clone())
            }
        }
        "BigUint" => {
            if a.clone() + b.clone()
                > I::try_from(u64::MAX).expect(&format!(
                    "expected <I> big enough to house u64::MAX, found: {}",
                    std::any::type_name::<I>()
                ))
            {
                format!(
                    "BigUint::parse_bytes(b\"{}\", 10).unwrap()",
                    a.clone() + b.clone()
                )
            } else {
                format!("{}u128.to_biguint().unwrap()", a.clone() + b.clone())
            }
        }
        _ => format!("{}_{output_ty}", a.clone() + b.clone()),
    }
}