const-str 1.1.0

compile-time string operations
Documentation
#![allow(unsafe_code)]

use super::str::StrBuf;
use crate::utf8::CharEncodeUtf8;

pub struct ToStr<T>(pub T);

impl ToStr<&str> {
    pub const fn output_len(&self) -> usize {
        self.0.len()
    }

    pub const fn const_eval<const N: usize>(&self) -> StrBuf<N> {
        StrBuf::from_str(self.0)
    }
}

impl ToStr<bool> {
    const fn bool_to_str(b: bool) -> &'static str {
        if b {
            "true"
        } else {
            "false"
        }
    }

    pub const fn output_len(&self) -> usize {
        Self::bool_to_str(self.0).len()
    }

    pub const fn const_eval<const N: usize>(&self) -> StrBuf<N> {
        let bytes: &[u8] = Self::bool_to_str(self.0).as_bytes();
        let buf = crate::bytes::merge([0; N], bytes);
        unsafe { StrBuf::new_unchecked(buf) }
    }
}

impl ToStr<char> {
    pub const fn output_len(&self) -> usize {
        self.0.len_utf8()
    }

    pub const fn const_eval<const N: usize>(&self) -> StrBuf<N> {
        let ch = CharEncodeUtf8::new(self.0);
        let buf = crate::bytes::merge([0; N], ch.as_bytes());
        unsafe { StrBuf::new_unchecked(buf) }
    }
}

macro_rules! impl_integer_to_str {
    ($unsigned: ty, $signed: ty) => {
        impl ToStr<$unsigned> {
            pub const fn output_len(&self) -> usize {
                let mut x = self.0;
                let mut ans = 1;
                while x > 9 {
                    ans += 1;
                    x /= 10;
                }
                ans
            }

            pub const fn const_eval<const N: usize>(&self) -> StrBuf<N> {
                let mut buf = [0; N];
                let mut pos = 0;
                let mut x = self.0;
                loop {
                    buf[pos] = b'0' + (x % 10) as u8;
                    pos += 1;
                    x /= 10;
                    if x == 0 {
                        break;
                    }
                }
                assert!(pos == N);
                let buf = crate::bytes::reversed(buf);
                unsafe { StrBuf::new_unchecked(buf) }
            }
        }

        impl ToStr<$signed> {
            pub const fn output_len(&self) -> usize {
                let x = self.0;
                let abs_len = ToStr(x.unsigned_abs()).output_len();
                abs_len + (x < 0) as usize
            }

            pub const fn const_eval<const N: usize>(&self) -> StrBuf<N> {
                let mut buf = [0; N];
                let mut pos = 0;

                let mut x = self.0.unsigned_abs();

                loop {
                    buf[pos] = b'0' + (x % 10) as u8;
                    pos += 1;
                    x /= 10;
                    if x == 0 {
                        break;
                    }
                }

                if self.0 < 0 {
                    buf[pos] = b'-';
                    pos += 1;
                }

                assert!(pos == N);
                let buf = crate::bytes::reversed(buf);
                unsafe { StrBuf::new_unchecked(buf) }
            }
        }
    };
}

impl_integer_to_str!(u8, i8);
impl_integer_to_str!(u16, i16);
impl_integer_to_str!(u32, i32);
impl_integer_to_str!(u64, i64);
impl_integer_to_str!(u128, i128);
impl_integer_to_str!(usize, isize);

/// Converts a value to a string slice.
///
/// The input type must be one of
///
/// + [`&str`]
/// + [`char`]
/// + [`bool`]
/// + [`u8`], [`u16`], [`u32`], [`u64`], [`u128`], [`usize`]
/// + [`i8`], [`i16`], [`i32`], [`i64`], [`i128`], [`isize`]
///
/// This macro is [const-context only](./index.html#const-context-only).
///
/// # Examples
///
/// ```
/// const A: &str = const_str::to_str!("A");
/// assert_eq!(A, "A");
///
/// const B: &str = const_str::to_str!('我');
/// assert_eq!(B, "我");
///
/// const C: &str = const_str::to_str!(true);
/// assert_eq!(C, "true");
///
/// const D: &str = const_str::to_str!(1_u8 + 1);
/// assert_eq!(D, "2");
///
/// const E: &str = const_str::to_str!(-21_i32 * 2);
/// assert_eq!(E, "-42")
/// ```
///
#[macro_export]
macro_rules! to_str {
    ($x: expr) => {{
        const OUTPUT_LEN: usize = $crate::__ctfe::ToStr($x).output_len();
        const OUTPUT_BUF: $crate::__ctfe::StrBuf<OUTPUT_LEN> =
            $crate::__ctfe::ToStr($x).const_eval();
        OUTPUT_BUF.as_str()
    }};
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_to_str() {
        macro_rules! test_to_str {
            ($ty: ty, $x: expr) => {{
                const X: $ty = $x;
                const OUTPUT_LEN: usize = ToStr(X).output_len();
                const OUTPUT_BUF: StrBuf<OUTPUT_LEN> = ToStr(X).const_eval();

                let output = OUTPUT_BUF.as_str();
                let ans = X.to_string();
                assert_eq!(OUTPUT_LEN, ans.len());
                assert_eq!(output, ans);
            }};
        }

        test_to_str!(&str, "lovelive superstar");

        test_to_str!(bool, true);
        test_to_str!(bool, false);

        test_to_str!(char, '');
        test_to_str!(char, '');

        test_to_str!(u8, 0);
        test_to_str!(u16, 0);
        test_to_str!(u32, 0);
        test_to_str!(u64, 0);
        test_to_str!(u128, 0);

        test_to_str!(u8, 10);
        test_to_str!(u8, 128);
        test_to_str!(u8, u8::MAX);

        test_to_str!(u64, 1);
        test_to_str!(u64, 10);
        test_to_str!(u64, 42);
        test_to_str!(u64, u64::MAX);

        test_to_str!(u128, u128::MAX);

        test_to_str!(i8, 0);
        test_to_str!(i16, 0);
        test_to_str!(i32, 0);
        test_to_str!(i64, 0);
        test_to_str!(i128, 0);

        test_to_str!(i8, -10);
        test_to_str!(i8, -42);
        test_to_str!(i8, i8::MAX);
        test_to_str!(i8, i8::MIN);

        test_to_str!(i64, 1);
        test_to_str!(i64, 10);
        test_to_str!(i64, -42);
        test_to_str!(i64, i64::MAX);
        test_to_str!(i64, i64::MIN);

        test_to_str!(i128, i128::MAX);
        test_to_str!(i128, i128::MIN);
    }

    #[test]
    fn test_to_str_runtime() {
        // Runtime tests for ToStr with various types
        let to_str_bool = ToStr(true);
        let buf = to_str_bool.const_eval::<4>();
        assert_eq!(buf.as_str(), "true");

        let to_str_char = ToStr('A');
        let buf2 = to_str_char.const_eval::<1>();
        assert_eq!(buf2.as_str(), "A");

        let to_str_str = ToStr("hello");
        let buf3 = to_str_str.const_eval::<5>();
        assert_eq!(buf3.as_str(), "hello");

        // Test various integer types
        let to_str_u8 = ToStr(42u8);
        let len = to_str_u8.output_len();
        assert_eq!(len, 2);

        let to_str_i8 = ToStr(-42i8);
        let len2 = to_str_i8.output_len();
        assert_eq!(len2, 3);

        let to_str_u64 = ToStr(12345u64);
        let len3 = to_str_u64.output_len();
        assert_eq!(len3, 5);

        let to_str_i64 = ToStr(-9876i64);
        let len4 = to_str_i64.output_len();
        assert_eq!(len4, 5);

        // Test max/min values
        let to_str_u8_max = ToStr(u8::MAX);
        let buf_max = to_str_u8_max.const_eval::<3>();
        assert_eq!(buf_max.as_str(), "255");

        let to_str_i8_min = ToStr(i8::MIN);
        let buf_min = to_str_i8_min.const_eval::<4>();
        assert_eq!(buf_min.as_str(), "-128");

        // Test output_len for str, bool, char
        let to_str_str = ToStr("test");
        assert_eq!(to_str_str.output_len(), 4);

        let to_str_bool_true = ToStr(true);
        assert_eq!(to_str_bool_true.output_len(), 4);

        let to_str_bool_false = ToStr(false);
        assert_eq!(to_str_bool_false.output_len(), 5);

        let to_str_char = ToStr('A');
        assert_eq!(to_str_char.output_len(), 1);

        let to_str_char_utf8 = ToStr('');
        assert_eq!(to_str_char_utf8.output_len(), 3);
    }
}