const_str/__ctfe/
concat.rs

1#![allow(unsafe_code)]
2
3use super::StrBuf;
4
5pub struct Concat<'a>(pub &'a [&'a str]);
6
7impl Concat<'_> {
8    pub const fn output_len(&self) -> usize {
9        let mut ans = 0;
10        let mut iter = self.0;
11        while let [x, xs @ ..] = iter {
12            ans += x.len();
13            iter = xs;
14        }
15        ans
16    }
17
18    pub const fn const_eval<const N: usize>(&self) -> StrBuf<N> {
19        let mut buf = [0; N];
20        let mut pos = 0;
21
22        let mut iter = self.0;
23        while let [x, xs @ ..] = iter {
24            let x = x.as_bytes();
25            let mut i = 0;
26            while i < x.len() {
27                buf[pos] = x[i];
28                pos += 1;
29                i += 1;
30            }
31            iter = xs;
32        }
33        assert!(pos == N);
34
35        unsafe { StrBuf::new_unchecked(buf) }
36    }
37}
38
39/// Concatenates values into a string slice.
40///
41/// The input type must be one of
42///
43/// + [`&str`]
44/// + [`char`]
45/// + [`bool`]
46/// + [`u8`], [`u16`], [`u32`], [`u64`], [`u128`], [`usize`]
47/// + [`i8`], [`i16`], [`i32`], [`i64`], [`i128`], [`isize`]
48///
49/// This macro is [const-context only](./index.html#const-context-only).
50///
51/// # Examples
52///
53/// ```
54/// const PROMPT: &str = "The answer is";
55/// const ANSWER: usize = 42;
56/// const MESSAGE: &str = const_str::concat!(PROMPT, " ", ANSWER);
57///
58/// assert_eq!(MESSAGE, "The answer is 42");
59/// ```
60///
61#[macro_export]
62macro_rules! concat {
63    ($($x: expr),+ $(,)?) => {{
64        const STRS: &[&str] = &[$( $crate::to_str!($x) ),+];
65        const OUTPUT_LEN: usize = $crate::__ctfe::Concat(STRS).output_len();
66        const OUTPUT_BUF: $crate::__ctfe::StrBuf<OUTPUT_LEN> = $crate::__ctfe::Concat(STRS).const_eval();
67        OUTPUT_BUF.as_str()
68    }}
69}
70
71pub struct Join<'a>(pub &'a [&'a str], pub &'a str);
72
73impl Join<'_> {
74    pub const fn output_len(&self) -> usize {
75        let mut ans = 0;
76        let mut i = 0;
77        while i < self.0.len() {
78            ans += self.0[i].len();
79            if i < self.0.len() - 1 {
80                ans += self.1.len();
81            }
82            i += 1;
83        }
84        ans
85    }
86
87    pub const fn const_eval<const N: usize>(&self) -> StrBuf<N> {
88        let mut buf = [0; N];
89        let mut pos = 0;
90
91        let mut i = 0;
92        while i < self.0.len() {
93            let x = self.0[i].as_bytes();
94            let mut j = 0;
95            while j < x.len() {
96                buf[pos] = x[j];
97                pos += 1;
98                j += 1;
99            }
100            if i < self.0.len() - 1 {
101                let sep = self.1.as_bytes();
102                let mut j = 0;
103                while j < sep.len() {
104                    buf[pos] = sep[j];
105                    pos += 1;
106                    j += 1;
107                }
108            }
109            i += 1;
110        }
111
112        unsafe { StrBuf::new_unchecked(buf) }
113    }
114}
115
116/// Concatenates string slices into a string slice, separated by a given separator.
117///
118/// This macro is [const-context only](./index.html#const-context-only).
119///
120/// # Examples
121///
122/// ```
123/// const WORDS: &[&str] = &["hello", "world"];
124/// const MESSAGE1: &str = const_str::join!(WORDS, " ");
125/// assert_eq!(MESSAGE1, "hello world");
126///
127/// const NUMS: &[&str] = &["1", "2", "3"];
128/// const MESSAGE2: &str = const_str::join!(NUMS, ", ");
129/// assert_eq!(MESSAGE2, "1, 2, 3");
130///
131/// const EMPTY: &[&str] = &[];
132/// const MESSAGE3: &str = const_str::join!(EMPTY, ", ");
133/// assert_eq!(MESSAGE3, "");
134/// ```
135#[macro_export]
136macro_rules! join {
137    ($strs: expr, $sep: expr) => {{
138        const STRS: &[&str] = $strs;
139        const SEP: &str = $sep;
140        const OUTPUT_LEN: usize = $crate::__ctfe::Join(STRS, SEP).output_len();
141        const OUTPUT_BUF: $crate::__ctfe::StrBuf<OUTPUT_LEN> =
142            $crate::__ctfe::Join(STRS, SEP).const_eval();
143        OUTPUT_BUF.as_str()
144    }};
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150
151    #[test]
152    fn test_concat() {
153        const PROMPT: &str = "The answer is";
154        const ANSWER: usize = 42;
155        const MESSAGE: &str = concat!(PROMPT, " ", ANSWER);
156        assert_eq!(MESSAGE, "The answer is 42");
157
158        const S1: &str = concat!("hello", " ", "world");
159        assert_eq!(S1, "hello world");
160
161        const S2: &str = concat!("a", "b", "c");
162        assert_eq!(S2, "abc");
163
164        const S3: &str = concat!("single");
165        assert_eq!(S3, "single");
166
167        const S4: &str = concat!("");
168        assert_eq!(S4, "");
169
170        const S5: &str = concat!(true, " ", false);
171        assert_eq!(S5, "true false");
172
173        const S6: &str = concat!('a', 'b', 'c');
174        assert_eq!(S6, "abc");
175    }
176
177    #[test]
178    fn test_concat_runtime() {
179        // Runtime tests to improve coverage
180        let strs = &["hello", "world"];
181        let concat = Concat(strs);
182        assert_eq!(concat.output_len(), 10);
183
184        let buf: StrBuf<10> = concat.const_eval();
185        assert_eq!(buf.as_str(), "helloworld");
186
187        let empty: &[&str] = &[];
188        let concat_empty = Concat(empty);
189        assert_eq!(concat_empty.output_len(), 0);
190
191        let single = &["test"];
192        let concat_single = Concat(single);
193        assert_eq!(concat_single.output_len(), 4);
194    }
195
196    #[test]
197    fn test_join() {
198        const WORDS: &[&str] = &["hello", "world"];
199        const MESSAGE1: &str = join!(WORDS, " ");
200        assert_eq!(MESSAGE1, "hello world");
201
202        const NUMS: &[&str] = &["1", "2", "3"];
203        const MESSAGE2: &str = join!(NUMS, ", ");
204        assert_eq!(MESSAGE2, "1, 2, 3");
205
206        const EMPTY: &[&str] = &[];
207        const MESSAGE3: &str = join!(EMPTY, ", ");
208        assert_eq!(MESSAGE3, "");
209
210        const SINGLE: &[&str] = &["alone"];
211        const MESSAGE4: &str = join!(SINGLE, ", ");
212        assert_eq!(MESSAGE4, "alone");
213
214        const MULTI: &[&str] = &["a", "b", "c", "d"];
215        const MESSAGE5: &str = join!(MULTI, "-");
216        assert_eq!(MESSAGE5, "a-b-c-d");
217    }
218
219    #[test]
220    fn test_join_runtime() {
221        // Runtime tests to improve coverage
222        let strs = &["hello", "world"];
223        let join = Join(strs, " ");
224        assert_eq!(join.output_len(), 11);
225
226        let buf: StrBuf<11> = join.const_eval();
227        assert_eq!(buf.as_str(), "hello world");
228
229        let empty: &[&str] = &[];
230        let join_empty = Join(empty, ", ");
231        assert_eq!(join_empty.output_len(), 0);
232
233        let single = &["test"];
234        let join_single = Join(single, ", ");
235        assert_eq!(join_single.output_len(), 4);
236
237        let multi = &["a", "b", "c"];
238        let join_multi = Join(multi, "-");
239        assert_eq!(join_multi.output_len(), 5); // "a-b-c"
240    }
241}