Skip to main content

g_string/
mutation.rs

1use core::fmt::Write;
2
3use crate::{AllowEmpty, GString, GStringError, Validator};
4
5// ------------------------------------------------------------------------------------
6// MUTATION APIs
7// ------------------------------------------------------------------------------------
8impl<V: Validator, const MIN: usize, const MAX: usize, const ASCII_ONLY: bool>
9    GString<V, MIN, MAX, ASCII_ONLY>
10{
11    /// Push a char at the end of string with invariants preserved.
12    pub fn push(&mut self, ch: char) -> Result<(), GStringError<V::Err>> {
13        // char takes up to 4 bytes
14        let mut buf = [0u8; 4];
15        let encoded = ch.encode_utf8(&mut buf);
16
17        self.push_str(encoded)
18    }
19
20    /// Push string at the end of string with invariants preserved.
21    pub fn push_str(&mut self, s: &str) -> Result<(), GStringError<V::Err>> {
22        let mut buf = [0u8; MAX];
23
24        // copy current content
25        buf[..self.len].copy_from_slice(&self.buf[..self.len]);
26
27        // append new content
28        let bytes = s.as_bytes();
29        let end = self.len + bytes.len();
30
31        if end > MAX {
32            return Err(GStringError::TooLong(MAX));
33        }
34
35        buf[self.len..end].copy_from_slice(bytes);
36
37        // SAFETY:
38        // existing bytes are valid UTF-8
39        // appended bytes come from &str
40        // concatenation of valid UTF-8 is valid UTF-8
41        let candidate = unsafe { core::str::from_utf8_unchecked(&buf[..end]) };
42
43        // fully revalidate through centralized constructor
44        *self = Self::try_new(candidate)?;
45
46        Ok(())
47    }
48
49    /// Insert a char at specific index with invariants preserved.
50    pub fn insert(&mut self, idx: usize, ch: char) -> Result<(), GStringError<V::Err>> {
51        let mut buf = [0u8; 4];
52        let encoded = ch.encode_utf8(&mut buf);
53
54        self.insert_str(idx, encoded)
55    }
56
57    /// Insert string at specific index with invariants preserved.
58    pub fn insert_str(&mut self, idx: usize, string: &str) -> Result<(), GStringError<V::Err>> {
59        if !self.as_str().is_char_boundary(idx) {
60            return Err(GStringError::Mutation("idx is not a char boundary"));
61        }
62
63        let insert_bytes = string.as_bytes();
64        let insert_len = insert_bytes.len();
65
66        let new_len = self.len + insert_len;
67
68        // early capacity check to avoid panic
69        if new_len > MAX {
70            return Err(GStringError::TooLong(MAX));
71        }
72
73        let mut buf = [0u8; MAX];
74
75        // before insertion point
76        buf[..idx].copy_from_slice(&self.buf[..idx]);
77
78        // inserted bytes
79        buf[idx..idx + insert_len].copy_from_slice(insert_bytes);
80
81        let tail_len = self.len - idx;
82
83        // after insertion point
84        buf[idx + insert_len..idx + insert_len + tail_len]
85            .copy_from_slice(&self.buf[idx..self.len]);
86
87        // SAFETY:
88        // - original bytes are valid UTF-8
89        // - inserted string is valid UTF-8
90        // - insertion only happens at char boundary
91        // - concatenation of valid UTF-8 is valid UTF-8
92        let candidate = unsafe { core::str::from_utf8_unchecked(&buf[..new_len]) };
93
94        *self = Self::try_new(candidate)?;
95
96        Ok(())
97    }
98
99    /// Pop a char from the end of string with invariants preserved.
100    pub fn pop(&mut self) -> Result<Option<char>, GStringError<V::Err>> {
101        if self.is_empty() {
102            return Ok(None);
103        }
104
105        let s = self.as_str();
106        let ch = s.chars().next_back();
107        let ch = if let Some(ch) = ch {
108            ch
109        } else {
110            return Ok(None);
111        };
112
113        let new_len = self.len - ch.len_utf8();
114
115        // SAFETY:
116        // truncating at char boundary preserves UTF-8 validity
117        let candidate = unsafe { core::str::from_utf8_unchecked(&self.buf[..new_len]) };
118
119        match Self::try_new(candidate) {
120            Ok(new) => {
121                *self = new;
122                Ok(Some(ch))
123            }
124            Err(err) => Err(err),
125        }
126    }
127
128    /// Removes a char at specific index with invariants preserved.
129    pub fn remove(&mut self, idx: usize) -> Result<char, GStringError<V::Err>> {
130        if !self.as_str().is_char_boundary(idx) {
131            return Err(GStringError::Mutation("idx is not a char boundary"));
132        }
133
134        let s = self.as_str();
135
136        let ch = s[idx..].chars().next().ok_or(GStringError::Mutation(
137            "cannot remove char from empty index",
138        ))?;
139
140        let ch_len = ch.len_utf8();
141
142        let new_len = self.len - ch_len;
143
144        let mut buf = [0u8; MAX];
145
146        // before removed char
147        buf[..idx].copy_from_slice(&self.buf[..idx]);
148
149        // after removed char
150        buf[idx..new_len].copy_from_slice(&self.buf[idx + ch_len..self.len]);
151
152        // SAFETY:
153        // removal at char boundary preserves UTF-8 validity
154        let candidate = unsafe { core::str::from_utf8_unchecked(&buf[..new_len]) };
155
156        *self = Self::try_new(candidate)?;
157
158        Ok(ch)
159    }
160
161    /// Truncates to a new length with invariants preserved.
162    pub fn truncate(&mut self, new_len: usize) -> Result<(), GStringError<V::Err>> {
163        if new_len >= self.len {
164            return Ok(());
165        }
166
167        if !self.as_str().is_char_boundary(new_len) {
168            return Err(GStringError::Mutation("new_len is not a char boundary"));
169        }
170
171        // SAFETY:
172        // truncating at char boundary preserves UTF-8 validity
173        let candidate = unsafe { core::str::from_utf8_unchecked(&self.buf[..new_len]) };
174
175        *self = Self::try_new(candidate)?;
176
177        Ok(())
178    }
179
180    /// Replaces `from` with `to` in string with invariants preserved.
181    #[cfg(feature = "alloc")]
182    pub fn replace(&mut self, from: &str, to: &str) -> Result<(), GStringError<V::Err>> {
183        // .replace requires allocation
184        let replaced = self.as_str().replace(from, to);
185
186        *self = Self::try_new(&replaced)?;
187
188        Ok(())
189    }
190
191    /// Replaces with range with invariants preserved.
192    pub fn replace_range<R>(
193        &mut self,
194        range: R,
195        replace_with: &str,
196    ) -> Result<(), GStringError<V::Err>>
197    where
198        R: core::ops::RangeBounds<usize>,
199    {
200        use core::ops::Bound;
201
202        let start = match range.start_bound() {
203            Bound::Included(&n) => n,
204            Bound::Excluded(&n) => n
205                .checked_add(1)
206                .ok_or(GStringError::Mutation("range overflow"))?,
207            Bound::Unbounded => 0,
208        };
209
210        let end = match range.end_bound() {
211            Bound::Included(&n) => n
212                .checked_add(1)
213                .ok_or(GStringError::Mutation("range overflow"))?,
214            Bound::Excluded(&n) => n,
215            Bound::Unbounded => self.len,
216        };
217
218        let s = self.as_str();
219
220        if !s.is_char_boundary(start) {
221            return Err(GStringError::Mutation("start range is not within boundary"));
222        }
223        if !s.is_char_boundary(end) {
224            return Err(GStringError::Mutation("end range is not within boundary"));
225        }
226        if start > end {
227            return Err(GStringError::Mutation(
228                "start range cannot be bigger than end",
229            ));
230        }
231
232        let mut buf = [0u8; MAX];
233
234        let before = &self.buf[..start];
235        let middle = replace_with.as_bytes();
236        let after = &self.buf[end..self.len];
237
238        let new_len = before
239            .len()
240            .checked_add(middle.len())
241            .and_then(|n| n.checked_add(after.len()))
242            .ok_or(GStringError::TooLong(MAX))?;
243
244        if new_len > MAX {
245            return Err(GStringError::TooLong(MAX));
246        }
247
248        // before
249        buf[..before.len()].copy_from_slice(before);
250
251        // replacement
252        buf[before.len()..before.len() + middle.len()].copy_from_slice(middle);
253
254        // after
255        buf[before.len() + middle.len()..new_len].copy_from_slice(after);
256
257        // SAFETY:
258        // - original string valid UTF-8
259        // - replace_with valid UTF-8
260        // - slicing only at char boundaries
261        let candidate = unsafe { core::str::from_utf8_unchecked(&buf[..new_len]) };
262
263        *self = Self::try_new(candidate)?;
264
265        Ok(())
266    }
267}
268
269impl<V: Validator + AllowEmpty, const MAX: usize, const ASCII_ONLY: bool>
270    GString<V, 0, MAX, ASCII_ONLY>
271{
272    /// Clear the string, only work if Validator implements `AllowEmpty` and MIN == 0.
273    #[inline]
274    pub fn clear(&mut self) {
275        *self = Self::default()
276    }
277}
278
279impl<V: Validator, const MIN: usize, const MAX: usize, const ASCII_ONLY: bool>
280    GString<V, MIN, MAX, ASCII_ONLY>
281{
282    /// Extend existing string with iterator of `AsRef<str>` with invariants preserved.
283    ///
284    /// # Examples
285    /// ```rust
286    /// use g_string::GString;
287    ///
288    /// let mut gs: GString<(), 0, 100, false> = GString::try_new("hello").unwrap();
289    /// gs.try_extend(["123", "456"].iter().copied());
290    /// assert_eq!(gs.as_str(), "hello123456");
291    /// ```
292    pub fn try_extend<I, S>(&mut self, iter: I) -> Result<(), GStringError<V::Err>>
293    where
294        I: IntoIterator<Item = S>,
295        S: AsRef<str>,
296    {
297        let mut tmp = self.clone();
298
299        for s in iter {
300            tmp.push_str(s.as_ref())?;
301        }
302
303        *self = tmp;
304
305        Ok(())
306    }
307
308    /// Extend existing string with iterator of chars with invariants preserved.
309    ///
310    /// # Examples
311    /// ```rust
312    /// use g_string::GString;
313    ///
314    /// let mut gs: GString<(), 0, 100, false> = GString::try_new("hello").unwrap();
315    /// gs.try_extend_chars(['@', 'z'].iter().copied());
316    /// assert_eq!(gs.as_str(), "hello@z");
317    /// ```
318    pub fn try_extend_chars<I>(&mut self, iter: I) -> Result<(), GStringError<V::Err>>
319    where
320        I: IntoIterator<Item = char>,
321    {
322        let mut tmp = self.clone();
323
324        for ch in iter {
325            tmp.push(ch)?;
326        }
327
328        *self = tmp;
329
330        Ok(())
331    }
332}
333
334impl<V: Validator, const MIN: usize, const MAX: usize, const ASCII_ONLY: bool> Write
335    for GString<V, MIN, MAX, ASCII_ONLY>
336{
337    fn write_str(&mut self, s: &str) -> core::fmt::Result {
338        self.push_str(s).map_err(|_| core::fmt::Error)
339    }
340
341    fn write_char(&mut self, c: char) -> core::fmt::Result {
342        self.push(c).map_err(|_| core::fmt::Error)
343    }
344}