const_str/__ctfe/
replace.rs

1#![allow(unsafe_code)]
2
3use crate::slice::advance;
4use crate::utf8::CharEncodeUtf8;
5
6use super::str::StrBuf;
7
8pub struct Replace<I, P, O>(pub I, pub P, pub O);
9
10impl Replace<&str, &str, &str> {
11    pub const fn output_len(&self) -> usize {
12        let Self(mut input, replace_from, replace_to) = *self;
13
14        if replace_from.is_empty() {
15            let input_chars = crate::utf8::str_count_chars(self.0);
16            input.len() + (input_chars + 1) * replace_to.len()
17        } else {
18            let mut ans = 0;
19            while let Some((pos, remain)) = crate::str::next_match(input, replace_from) {
20                ans += pos + replace_to.len();
21                input = remain;
22            }
23            ans += input.len();
24            ans
25        }
26    }
27
28    pub const fn const_eval<const N: usize>(&self) -> StrBuf<N> {
29        let Self(input, replace_from, replace_to) = *self;
30
31        let mut buf = [0; N];
32        let mut pos = 0;
33
34        macro_rules! push {
35            ($x: expr) => {{
36                buf[pos] = $x;
37                pos += 1;
38            }};
39        }
40
41        if replace_from.is_empty() {
42            let mut input = input.as_bytes();
43            let replace_to = replace_to.as_bytes();
44            loop {
45                let mut k = 0;
46                while k < replace_to.len() {
47                    push!(replace_to[k]);
48                    k += 1;
49                }
50
51                let count = match crate::utf8::next_char(input) {
52                    Some((_, count)) => count,
53                    None => break,
54                };
55
56                let mut i = 0;
57                while i < count {
58                    push!(input[i]);
59                    i += 1;
60                }
61
62                input = advance(input, count);
63            }
64        } else {
65            let mut input = input;
66            let replace_to = replace_to.as_bytes();
67
68            while let Some((pos, remain)) = crate::str::next_match(input, replace_from) {
69                let mut i = 0;
70                while i < pos {
71                    push!(input.as_bytes()[i]);
72                    i += 1;
73                }
74                let mut k = 0;
75                while k < replace_to.len() {
76                    push!(replace_to[k]);
77                    k += 1;
78                }
79                input = remain;
80            }
81
82            let input = input.as_bytes();
83            let mut i = 0;
84            while i < input.len() {
85                push!(input[i]);
86                i += 1;
87            }
88        }
89
90        assert!(pos == N);
91        unsafe { StrBuf::new_unchecked(buf) }
92    }
93}
94
95impl Replace<&str, char, &str> {
96    pub const fn output_len(&self) -> usize {
97        let ch = CharEncodeUtf8::new(self.1);
98        Replace(self.0, ch.as_str(), self.2).output_len()
99    }
100    pub const fn const_eval<const N: usize>(&self) -> StrBuf<N> {
101        let ch = CharEncodeUtf8::new(self.1);
102        Replace(self.0, ch.as_str(), self.2).const_eval()
103    }
104}
105
106/// Replaces all matches of a pattern with another string slice.
107///
108/// See [`str::replace`](https://doc.rust-lang.org/std/primitive.str.html#method.replace).
109///
110/// The pattern type must be one of
111///
112/// + [`&str`](str)
113/// + [`char`]
114///
115/// This macro is [const-context only](./index.html#const-context-only).
116///
117/// # Examples
118///
119/// ```
120/// assert_eq!("this is new", const_str::replace!("this is old", "old", "new"));
121/// ```
122///
123#[macro_export]
124macro_rules! replace {
125    ($s: expr, $from: expr, $to: expr) => {{
126        const OUTPUT_LEN: usize = $crate::__ctfe::Replace($s, $from, $to).output_len();
127        const OUTPUT_BUF: $crate::__ctfe::StrBuf<OUTPUT_LEN> =
128            $crate::__ctfe::Replace($s, $from, $to).const_eval();
129        OUTPUT_BUF.as_str()
130    }};
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    #[test]
138    fn test_replace() {
139        macro_rules! testcase {
140            ($input: expr, $from: expr, $to: expr) => {{
141                const OUTPUT_LEN: usize = Replace($input, $from, $to).output_len();
142                const OUTPUT_BUF: StrBuf<OUTPUT_LEN> = Replace($input, $from, $to).const_eval();
143                const OUTPUT: &str = OUTPUT_BUF.as_str();
144
145                let ans = $input.replace($from, $to);
146                assert_eq!(OUTPUT, &*ans, "ans = {:?}", ans);
147                assert_eq!(OUTPUT_LEN, ans.len());
148            }};
149        }
150
151        testcase!("", "", "");
152        testcase!("", "", "a");
153        testcase!("", "a", "");
154        testcase!("", "a", "b");
155        testcase!("a", "", "b");
156        testcase!("asd", "", "b");
157        testcase!("aba", "a", "c");
158        testcase!("this is old", "old", "new");
159        testcase!("我", "", "1");
160        testcase!("我", "", "我");
161        testcase!("我", "我", "");
162        testcase!("aaaa", "aa", "bb");
163        testcase!("run / v4", " ", "");
164        testcase!("token", " ", "");
165        testcase!("v4 / udp", " ", "");
166        testcase!("v4 / upnp", "p", "");
167
168        testcase!("", 'a', "");
169        testcase!("", 'a', "b");
170        testcase!("aba", 'a', "c");
171        testcase!("run / v4", ' ', "");
172        testcase!("token", ' ', "");
173        testcase!("v4 / udp", ' ', "");
174        testcase!("我", '我', "");
175    }
176
177    #[test]
178    fn test_replace_runtime() {
179        // Runtime tests for Replace
180        let replace1 = Replace("hello world", "world", "rust");
181        assert_eq!(replace1.output_len(), 10);
182        let buf1: StrBuf<10> = replace1.const_eval();
183        assert_eq!(buf1.as_str(), "hello rust");
184
185        let replace2 = Replace("aaa", "a", "bb");
186        assert_eq!(replace2.output_len(), 6);
187        let buf2: StrBuf<6> = replace2.const_eval();
188        assert_eq!(buf2.as_str(), "bbbbbb");
189
190        let replace3 = Replace("test", "x", "y");
191        assert_eq!(replace3.output_len(), 4);
192        let buf3: StrBuf<4> = replace3.const_eval();
193        assert_eq!(buf3.as_str(), "test");
194
195        let replace_empty = Replace("", "a", "b");
196        let len_empty = replace_empty.output_len();
197        assert_eq!(len_empty, 0);
198
199        // Test with char pattern
200        let replace_char = Replace("hello", 'l', "L");
201        assert_eq!(replace_char.output_len(), 5);
202        let buf_char: StrBuf<5> = replace_char.const_eval();
203        assert_eq!(buf_char.as_str(), "heLLo");
204    }
205
206    #[test]
207    fn test_replace_empty_pattern() {
208        // Test replacing with empty "from" pattern
209        // This inserts "to" between every character
210
211        // Empty string with empty pattern
212        let r1 = Replace("", "", "");
213        assert_eq!(r1.output_len(), 0);
214        let buf1: StrBuf<0> = r1.const_eval();
215        assert_eq!(buf1.as_str(), "");
216
217        // Empty string, empty pattern, non-empty replacement
218        let r2 = Replace("", "", "x");
219        assert_eq!(r2.output_len(), 1);
220        let buf2: StrBuf<1> = r2.const_eval();
221        assert_eq!(buf2.as_str(), "x");
222
223        // Single char with empty pattern
224        let r3 = Replace("a", "", "x");
225        assert_eq!(r3.output_len(), 3);
226        let buf3: StrBuf<3> = r3.const_eval();
227        assert_eq!(buf3.as_str(), "xax");
228
229        // Multiple chars with empty pattern
230        let r4 = Replace("ab", "", "x");
231        assert_eq!(r4.output_len(), 5);
232        let buf4: StrBuf<5> = r4.const_eval();
233        assert_eq!(buf4.as_str(), "xaxbx");
234
235        // Multi-byte UTF-8 character with empty pattern
236        let r5 = Replace("我", "", "x");
237        assert_eq!(r5.output_len(), 5);
238        let buf5: StrBuf<5> = r5.const_eval();
239        assert_eq!(buf5.as_str(), "x我x");
240
241        // Multiple multi-byte characters with empty pattern
242        let r6 = Replace("我好", "", "x");
243        assert_eq!(r6.output_len(), 9);
244        let buf6: StrBuf<9> = r6.const_eval();
245        assert_eq!(buf6.as_str(), "x我x好x");
246    }
247}