Skip to main content

textos/unicode/string/
u8string.rs

1// textos::unicode::string::static
2//
3//! `String` backed by an array.
4//
5// TOC
6// - definitions
7// - trait impls
8// - conversions
9// - tests
10
11#[cfg(feature = "alloc")]
12use alloc::{ffi::CString, str::Chars, string::ToString};
13
14use crate::{
15    error::{TextosError as Error, TextosResult as Result},
16    macros::impl_sized_alias,
17    unicode::char::*,
18};
19use core::{fmt, ops::Deref};
20use devela::codegen::paste;
21
22/* definitions */
23
24/// A UTF-8-encoded string, backed by an array,
25/// with 255 bytes of maximum constant capacity.
26///
27/// Internally, the current length is stored as a [`u8`].
28#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
29pub struct StaticU8String<const CAP: usize> {
30    // WAITING for when we can use CAP: u8 for panic-less const boundary check.
31    arr: [u8; CAP],
32    len: u8,
33}
34
35impl_sized_alias![
36    String, StaticU8String,
37    "UTF-8-encoded string, with fixed capacity of ", ".":
38    "A" 16, 1 "";
39    "A" 24, 2 "s";
40    "A" 32, 3 "s";
41    "A" 40, 4 "s";
42    "A" 48, 5 "s";
43    "A" 56, 6 "s";
44    "A" 64, 7 "s";
45    "A" 128, 15 "s";
46    "A" 256, 31 "s";
47    "A" 512, 63 "s";
48    "A" 1024, 127 "s";
49    "A" 2048, 255 "s"
50];
51
52impl<const CAP: usize> StaticU8String<CAP> {
53    /// Creates a new empty `StaticU8String`.
54    ///
55    /// # Panics
56    /// Panics if `CAP` > 255.
57    #[inline]
58    pub const fn new() -> Self {
59        assert![CAP <= 255];
60        Self {
61            arr: [0; CAP],
62            len: 0,
63        }
64    }
65
66    /// Creates a new `StaticU8String` from a `Char7`.
67    ///
68    /// # Panic
69    /// Panics if `CAP` > 255 or < 1.
70    ///
71    /// Will never panic if `CAP` >= 1 and <= 255.
72    #[inline]
73    pub const fn from_char7(c: Char7) -> Self {
74        let mut new = Self::new();
75        new.arr[0] = c.to_utf8_bytes()[0];
76        new.len = 1;
77        new
78    }
79
80    /// Creates a new `StaticU8String` from a `Char8`.
81    ///
82    /// # Panic
83    /// Panics if `CAP` > 255 or < `c.`[`len_utf8()`][Char8#method.len_utf8].
84    ///
85    /// Will never panic if `CAP` >= 2 and <= 255.
86    #[inline]
87    pub const fn from_char8(c: Char8) -> Self {
88        let mut new = Self::new();
89
90        let bytes = c.to_utf8_bytes();
91        new.len = char_utf8_2bytes_len(bytes);
92
93        new.arr[0] = bytes[0];
94        if new.len > 1 {
95            new.arr[1] = bytes[1];
96        }
97        new
98    }
99
100    /// Creates a new `StaticU8String` from a `Char16`.
101    ///
102    /// # Panic
103    /// Panics if `CAP` > 255 or < `c.`[`len_utf8()`][Char16#method.len_utf8].
104    ///
105    /// Will never panic if `CAP` >= 3 and <= 255.
106    #[inline]
107    pub const fn from_char16(c: Char16) -> Self {
108        let mut new = Self::new();
109
110        let bytes = c.to_utf8_bytes();
111        new.len = char_utf8_3bytes_len(bytes);
112
113        new.arr[0] = bytes[0];
114        if new.len > 1 {
115            new.arr[1] = bytes[1];
116        }
117        if new.len > 2 {
118            new.arr[2] = bytes[2];
119        }
120        new
121    }
122
123    /// Creates a new `StaticU8String` from a `Char24`.
124    ///
125    /// # Panic
126    /// Panics if `CAP` > 255 or < `c.`[`len_utf8()`][Char24#method.len_utf8].
127    ///
128    /// Will never panic if `CAP` >= 4 and <= 255.
129    #[inline]
130    pub const fn from_char24(c: Char24) -> Self {
131        let mut new = Self::new();
132
133        let bytes = c.to_utf8_bytes();
134        new.len = char_utf8_4bytes_len(bytes);
135
136        new.arr[0] = bytes[0];
137        if new.len > 1 {
138            new.arr[1] = bytes[1];
139        }
140        if new.len > 2 {
141            new.arr[2] = bytes[2];
142        }
143        if new.len > 3 {
144            new.arr[3] = bytes[3];
145        }
146        new
147    }
148
149    /// Creates a new `StaticU8String` from a `Char32`.
150    ///
151    /// # Panic
152    /// Panics if `CAP` > 255 or < `c.`[`len_utf8()`][Char32#method.len_utf8].
153    ///
154    /// Will never panic if `CAP` >= 4 and <= 255.
155    #[inline]
156    pub const fn from_char32(c: Char32) -> Self {
157        let mut new = Self::new();
158
159        let bytes = c.to_utf8_bytes();
160        new.len = char_utf8_4bytes_len(bytes);
161
162        new.arr[0] = bytes[0];
163        if new.len > 1 {
164            new.arr[1] = bytes[1];
165        }
166        if new.len > 2 {
167            new.arr[2] = bytes[2];
168        }
169        if new.len > 3 {
170            new.arr[3] = bytes[3];
171        }
172        new
173    }
174
175    /// Creates a new `StaticU8String` from a `char`.
176    ///
177    /// # Panic
178    /// Panics if `CAP` > 255 or < `c.`[`len_utf8()`][Chars#method.len_utf8].
179    ///
180    /// Will never panic if `CAP` >= 4 and <= 255.
181    #[inline]
182    pub const fn from_char(c: char) -> Self {
183        Self::from_char32(Char32(c))
184    }
185
186    //
187
188    /// Returns the total capacity in bytes.
189    #[inline]
190    pub const fn capacity() -> usize {
191        CAP
192    }
193
194    /// Returns the remaining capacity.
195    #[inline]
196    pub const fn remaining_capacity(&self) -> usize {
197        CAP - self.len as usize
198    }
199
200    /// Returns the current length.
201    #[inline]
202    pub const fn len(&self) -> usize {
203        self.len as usize
204    }
205
206    /// Returns `true` if the current length is 0.
207    #[inline]
208    pub const fn is_empty(&self) -> bool {
209        self.len == 0
210    }
211
212    /// Returns `true` if the current remaining capacity is 0.
213    #[inline]
214    pub const fn is_full(&self) -> bool {
215        self.len == CAP as u8
216    }
217
218    /// Sets the length to 0.
219    #[inline]
220    pub fn clear(&mut self) {
221        self.len = 0;
222    }
223
224    /// Sets the length to 0, and resets all the bytes to 0.
225    #[inline]
226    pub fn reset(&mut self) {
227        self.arr = [0; CAP];
228        self.len = 0;
229    }
230
231    //
232
233    /// Returns a byte slice of the inner string slice.
234    #[inline]
235    pub fn as_bytes(&self) -> &[u8] {
236        #[cfg(feature = "unsafe")]
237        unsafe {
238            self.arr.get_unchecked(0..self.len as usize)
239        }
240
241        #[cfg(not(feature = "unsafe"))]
242        self.arr
243            .get(0..self.len as usize)
244            .expect("len must be <= arr.len()")
245    }
246
247    /// Returns a mutable byte slice of the inner string slice.
248    #[inline]
249    #[cfg(feature = "unsafe")]
250    #[cfg_attr(feature = "nightly", doc(cfg(feature = "unsafe")))]
251    pub unsafe fn as_bytes_mut(&mut self) -> &mut [u8] {
252        self.arr.get_unchecked_mut(0..self.len as usize)
253    }
254
255    /// Returns a copy of the inner array with the full contents.
256    ///
257    /// The array contains all the bytes, including those outside the current length.
258    #[inline]
259    pub const fn as_array(&self) -> [u8; CAP] {
260        self.arr
261    }
262
263    /// Returns the inner array with the full contents.
264    ///
265    /// The array contains all the bytes, including those outside the current length.
266    #[inline]
267    pub const fn into_array(self) -> [u8; CAP] {
268        self.arr
269    }
270
271    /// Returns the inner string slice.
272    pub fn as_str(&self) -> &str {
273        #[cfg(feature = "unsafe")]
274        unsafe {
275            core::str::from_utf8_unchecked(
276                self.arr
277                    .get(0..self.len as usize)
278                    .expect("len must be <= arr.len()"),
279            )
280        }
281        #[cfg(not(feature = "unsafe"))]
282        core::str::from_utf8(
283            self.arr
284                .get(0..self.len as usize)
285                .expect("len must be <= arr.len()"),
286        )
287        .expect("must be valid utf-8")
288    }
289
290    /// Returns the mutable inner string slice.
291    #[cfg(feature = "unsafe")]
292    #[cfg_attr(feature = "nightly", doc(cfg(feature = "unsafe")))]
293    pub fn as_str_mut(&mut self) -> &mut str {
294        unsafe { &mut *(self.as_bytes_mut() as *mut [u8] as *mut str) }
295    }
296
297    /// Returns an iterator over the `chars` of this grapheme cluster.
298    #[cfg(feature = "alloc")]
299    #[cfg_attr(feature = "nightly", doc(cfg(feature = "alloc")))]
300    pub fn chars(&self) -> Chars {
301        self.as_str().chars()
302    }
303
304    /// Returns a new allocated C-compatible, nul-terminanted string.
305    #[inline]
306    #[cfg(feature = "alloc")]
307    #[cfg_attr(feature = "nightly", doc(cfg(feature = "alloc")))]
308    pub fn to_cstring(&self) -> CString {
309        CString::new(self.to_string()).unwrap()
310    }
311
312    //
313
314    /// Removes the last character and returns it, or `None` if
315    /// the string is empty.
316    #[inline]
317    pub fn pop(&mut self) -> Option<char> {
318        self.as_str().chars().last().map(|c| {
319            self.len -= c.len_utf8() as u8;
320            c
321        })
322    }
323
324    /// Tries to remove the last character and returns it, or `None` if
325    /// the string is empty.
326    ///
327    /// # Errors
328    /// Returns an error if the string is empty.
329    #[inline]
330    pub fn try_pop(&mut self) -> Result<char> {
331        self.as_str()
332            .chars()
333            .last()
334            .map(|c| {
335                self.len -= c.len_utf8() as u8;
336                c
337            })
338            .ok_or(Error::NotEnoughElements(1))
339    }
340
341    /// Appends to the end of the string the given `character`.
342    ///
343    /// Returns the number of bytes written.
344    ///
345    /// It will return 0 bytes if the given `character` doesn't fit in
346    /// the remaining capacity.
347    pub fn push(&mut self, character: char) -> usize {
348        let char_len = character.len_utf8();
349        if self.remaining_capacity() >= char_len {
350            let beg = self.len as usize;
351            let end = beg + char_len;
352            let _ = character.encode_utf8(&mut self.arr[beg..end]);
353            self.len += char_len as u8;
354            char_len
355        } else {
356            0
357        }
358    }
359
360    /// Tries to append to the end of the string the given `character`.
361    ///
362    /// Returns the number of bytes written.
363    ///
364    /// # Errors
365    /// Errors if the capacity is not enough to hold the `character`.
366    pub fn try_push(&mut self, character: char) -> Result<usize> {
367        let char_len = character.len_utf8();
368        if self.remaining_capacity() >= char_len {
369            let beg = self.len as usize;
370            let end = beg + char_len;
371            let _ = character.encode_utf8(&mut self.arr[beg..end]);
372            self.len += char_len as u8;
373            Ok(char_len)
374        } else {
375            Err(Error::NotEnoughCapacity(char_len))
376        }
377    }
378}
379
380/* traits */
381
382impl<const CAP: usize> Default for StaticU8String<CAP> {
383    /// Returns an empty string.
384    ///
385    /// # Panics
386    /// Panics if `CAP` > 255.
387    #[inline]
388    fn default() -> Self {
389        Self::new()
390    }
391}
392
393impl<const CAP: usize> fmt::Display for StaticU8String<CAP> {
394    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
395        write!(f, "{}", self.as_str())
396    }
397}
398
399impl<const CAP: usize> fmt::Debug for StaticU8String<CAP> {
400    #[inline]
401    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
402        write!(f, "{:?}", self.as_str())
403    }
404}
405
406impl<const CAP: usize> Deref for StaticU8String<CAP> {
407    type Target = str;
408    fn deref(&self) -> &Self::Target {
409        self.as_str()
410    }
411}
412
413/* conversions */
414
415macro_rules! impl_from_char {
416    // $char:ty char type
417    // $for_name: `for` type name prefix
418    // $bit: size in bits.
419    ( $char:ty => $for_name:ident: $( $for_bit:expr ),+ ) => {
420        $( impl_from_char![@$char => $for_name: $for_bit]; )+
421    };
422    ( @$char:ty => $for_name:ident: $for_bit:expr ) => { paste! {
423        impl From<$char> for [< $for_name $for_bit >] {
424            fn from(c: $char) -> [< $for_name $for_bit >] {
425                let mut s = Self::default();
426                let _ = s.push(c.into());
427                s
428            }
429        }
430    }};
431    ( try $char:ty => $for_name:ident: $( $for_bit:expr ),+ ) => {
432        $( impl_from_char![@try $char => $for_name: $for_bit]; )+
433    };
434    ( @try $char:ty => $for_name:ident: $for_bit:expr ) => { paste! {
435        impl TryFrom<$char> for [< $for_name $for_bit >] {
436            type Error = Error;
437            fn try_from(c: $char) -> Result<[< $for_name $for_bit >]> {
438                let mut s = Self::default();
439                s.try_push(c.into())?;
440                Ok(s)
441            }
442        }
443    }};
444}
445impl_from_char![Char7 => String: 16, 24, 32, 40, 48, 56, 64, 128, 256, 512, 1024, 2048];
446impl_from_char![Char8 => String: 24, 32, 40, 48, 56, 64, 128, 256, 512, 1024, 2048];
447impl_from_char![try Char8 => String: 16];
448impl_from_char![Char16 => String: 32, 40, 48, 56, 64, 128, 256, 512, 1024, 2048];
449impl_from_char![try Char16 => String: 16, 24];
450impl_from_char![Char24 => String: 40, 48, 56, 64, 128, 256, 512, 1024, 2048];
451impl_from_char![try Char24 => String: 16, 24, 32];
452impl_from_char![Char32 => String: 40, 48, 56, 64, 128, 256, 512, 1024, 2048];
453impl_from_char![try Char32 => String: 16, 24, 32];
454impl_from_char![char => String: 40, 48, 56, 64, 128, 256, 512, 1024, 2048];
455impl_from_char![try char => String: 16, 24, 32];
456
457#[cfg(test)]
458mod tests {
459    use super::*;
460
461    #[test]
462    fn push() {
463        let mut s = String32::new(); // max capacity == 3
464
465        assert![s.try_push('ñ').is_ok()];
466        assert_eq![2, s.len()];
467        assert![s.try_push('ñ').is_err()];
468        assert_eq![2, s.len()];
469        assert![s.try_push('a').is_ok()];
470        assert_eq![3, s.len()];
471    }
472
473    // TODO
474    #[test]
475    fn pop() {
476        let mut s = String32::new(); // max capacity == 3
477
478        s.push('ñ');
479        s.push('a');
480        assert_eq![Some('a'), s.pop()];
481        assert_eq![Some('ñ'), s.pop()];
482        assert_eq![None, s.pop()];
483    }
484}