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 pub fn slice(&self, start: Number, end: Number) -> EinString {
40 let start = f64::from(start);
41 let end = f64::from(end);
42
43 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}