ein_ffi/
string.rs

1use super::{arc::ArcBuffer, number::Number};
2use std::{cmp::max, str::from_utf8_unchecked};
3
4#[repr(C)]
5#[derive(Clone, Debug)]
6pub struct EinString {
7    buffer: ArcBuffer,
8}
9
10impl EinString {
11    pub fn new(buffer: ArcBuffer) -> Self {
12        Self { buffer }
13    }
14
15    pub fn empty() -> Self {
16        Self {
17            buffer: ArcBuffer::new(0),
18        }
19    }
20
21    pub fn as_slice(&self) -> &[u8] {
22        self.buffer.as_slice()
23    }
24
25    pub fn len(&self) -> usize {
26        self.buffer.as_slice().len()
27    }
28
29    pub fn join(&self, other: &Self) -> Self {
30        let mut buffer = ArcBuffer::new(self.len() + other.len());
31
32        buffer.as_slice_mut()[..self.len()].copy_from_slice(self.as_slice());
33        buffer.as_slice_mut()[self.len()..].copy_from_slice(other.as_slice());
34
35        Self { buffer }
36    }
37
38    // Indices are inclusive and start from 1.
39    pub fn slice(&self, start: Number, end: Number) -> EinString {
40        let start = f64::from(start);
41        let end = f64::from(end);
42
43        // TODO Allow infinite ranges
44        if !start.is_finite() || !end.is_finite() {
45            return Self::empty();
46        }
47
48        let start = max(start as isize - 1, 0) as usize;
49        let end = max(end as isize, 0) as usize;
50
51        let string = unsafe { from_utf8_unchecked(self.as_slice()) };
52
53        if string.is_empty() || start >= string.chars().count() || end <= start {
54            Self::empty()
55        } else {
56            string[Self::get_byte_index(string, start)..Self::get_byte_index(string, end)].into()
57        }
58    }
59
60    fn get_byte_index(string: &str, index: usize) -> usize {
61        string
62            .char_indices()
63            .nth(index)
64            .map(|(index, _)| index)
65            .unwrap_or_else(|| string.as_bytes().len())
66    }
67}
68
69unsafe impl Sync for EinString {}
70
71impl Default for EinString {
72    fn default() -> Self {
73        Self {
74            buffer: ArcBuffer::new(0),
75        }
76    }
77}
78
79impl PartialEq for EinString {
80    fn eq(&self, other: &EinString) -> bool {
81        self.as_slice() == other.as_slice()
82    }
83}
84
85impl From<&[u8]> for EinString {
86    fn from(bytes: &[u8]) -> Self {
87        Self {
88            buffer: bytes.into(),
89        }
90    }
91}
92
93impl From<&str> for EinString {
94    fn from(string: &str) -> Self {
95        string.as_bytes().into()
96    }
97}
98
99impl From<String> for EinString {
100    fn from(string: String) -> Self {
101        string.as_str().into()
102    }
103}
104
105impl From<Vec<u8>> for EinString {
106    fn from(vec: Vec<u8>) -> Self {
107        vec.as_slice().into()
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    #[test]
116    fn join() {
117        assert_eq!(
118            EinString::from("foo").join(&EinString::from("bar")),
119            EinString::from("foobar")
120        );
121    }
122
123    #[test]
124    fn join_empty() {
125        assert_eq!(
126            EinString::from("").join(&EinString::from("")),
127            EinString::from("")
128        );
129    }
130
131    #[test]
132    fn slice_with_ascii() {
133        assert_eq!(
134            EinString::from("abc").slice(2.0.into(), 2.0.into()),
135            EinString::from("b")
136        );
137    }
138
139    #[test]
140    fn slice_with_negative_index() {
141        assert_eq!(
142            EinString::from("abc").slice((-1.0).into(), 3.0.into()),
143            EinString::from("abc")
144        );
145    }
146
147    #[test]
148    fn slice_into_whole() {
149        assert_eq!(
150            EinString::from("abc").slice(1.0.into(), 3.0.into()),
151            EinString::from("abc")
152        );
153    }
154
155    #[test]
156    fn slice_into_empty() {
157        assert_eq!(
158            EinString::from("abc").slice(4.0.into(), 4.0.into()),
159            EinString::from("")
160        );
161    }
162
163    #[test]
164    fn slice_with_emojis() {
165        assert_eq!(
166            EinString::from("😀😉😂").slice(2.0.into(), 2.0.into()),
167            EinString::from("😉")
168        );
169    }
170
171    #[test]
172    fn slice_last_with_emojis() {
173        assert_eq!(
174            EinString::from("😀😉😂").slice(3.0.into(), 3.0.into()),
175            EinString::from("😂")
176        );
177    }
178}