1#![crate_name = "indexable_str"]
2
3use std::{
4 fmt::Display,
5 ops::{Index, Range, RangeFrom, RangeTo},
6};
7
8#[derive(Copy, Clone)]
9struct CharOffset {
10 chr: char,
11 offset: usize,
12}
13
14pub struct IndexableStr<'a> {
82 str: &'a str,
83 str_length: usize,
84 chars_vec: Vec<CharOffset>,
85 chars_length: usize,
86}
87
88impl<'a> IndexableStr<'a> {
89 pub fn new(str: &'a str) -> IndexableStr {
100 let mut current_offset: usize = 0;
101
102 let chars_vec: Vec<CharOffset> = str.chars().map(|c| {
103 let char_offset = CharOffset {
104 chr: c,
105 offset: current_offset,
106 };
107
108 let code_point: u32 = c as u32;
109
110 current_offset += (|| {
111 if code_point <= 0x7F {
112 return 1;
113 }
114
115 if code_point <= 0x7FF {
116 return 2;
117 }
118
119 if code_point <= 0xFFFF {
120 return 3;
121 }
122
123 if code_point <= 0x10FFFF {
124 return 4;
125 }
126
127 0
128 })();
129
130 char_offset
131 }).collect();
132
133 let chars_length: usize = chars_vec.len();
134
135 IndexableStr {
136 str,
137 str_length: str.len(),
138 chars_vec,
139 chars_length,
140 }
141 }
142
143 pub fn as_str(&self) -> &'a str {
154 self.str
155 }
156
157 pub fn len(&self) -> usize {
168 self.chars_length
169 }
170
171 fn create_str_from_range(&self, start_index: usize, end_index: usize) -> &str {
172 if end_index > self.chars_length {
173 panic!("Range end: ({end_index}) must be less than or equal to the number of UTF-8 characters in the string ({})!", self.chars_length);
174 }
175
176 if end_index < start_index {
177 panic!("Range end: ({end_index} must be greater than or equal to Range start: ({start_index})!")
178 }
179
180 let bytes_start: usize = self.chars_vec[start_index].offset;
181 let bytes_end: usize = match end_index {
182 _val if self.chars_length == end_index => self.str_length,
183 _ => self.chars_vec[end_index].offset,
184 };
185
186 &self.str[bytes_start..bytes_end]
187 }
188}
189
190impl<'a> Display for IndexableStr<'a> {
191 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192 write!(f, "{}", self.str)
193 }
194}
195
196impl<'a> Index<usize> for IndexableStr<'a> {
197 type Output = char;
198
199 fn index(&self, index: usize) -> &char {
200 &self.chars_vec[index].chr
201 }
202}
203
204impl<'a> Index<Range<usize>> for IndexableStr<'a> {
208 type Output = str;
209
210 fn index(&self, range: Range<usize>) -> &Self::Output {
211 self.create_str_from_range(range.start, range.end)
212 }
213}
214
215
216
217impl<'a> Index<RangeFrom<usize>> for IndexableStr<'a> {
220 type Output = str;
221
222 fn index(&self, index: RangeFrom<usize>) -> &Self::Output {
223 self.create_str_from_range(index.start, self.chars_length)
224 }
225}
226
227
228
229impl<'a> Index<RangeTo<usize>> for IndexableStr<'a> {
232 type Output = str;
233
234 fn index(&self, index: RangeTo<usize>) -> &Self::Output {
235 self.create_str_from_range(0, index.end)
236 }
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242
243 #[test]
244 fn test_as_str_works() {
245 let s = IndexableStr::new("0😀23456789");
246
247 assert_eq!(s.as_str(), "0😀23456789");
248 }
249
250 #[test]
251 fn test_len_works() {
252 let s = IndexableStr::new("0😀23456789");
253
254 assert_eq!(s.len(), 10);
255 }
256
257 #[test]
258 fn test_to_string_works() {
259 let s = IndexableStr::new("0😀23456789");
260
261 assert_eq!(s.to_string(), "0😀23456789");
262 }
263 #[test]
264 fn test_index_works() {
265 let s = IndexableStr::new("0😀23456789");
266
267 assert_eq!(s[1], '😀');
268 }
269 #[test]
270 fn test_range_works() {
271 let s = IndexableStr::new("0😀23456789");
272
273 assert_eq!(&s[1..9], "😀2345678");
274 }
275 #[test]
276 fn test_range_from_works() {
277 let s = IndexableStr::new("0😀23456789");
278
279 assert_eq!(&s[1..], "😀23456789");
280 }
281
282 #[test]
283 fn test_range_to_works() {
284 let s = IndexableStr::new("0😀23456789");
285 println!("length: {}", s.as_str().len());
286
287 assert_eq!(&s[..9], "0😀2345678");
288 }
289
290 #[test]
291 fn test_range_when_last_character_is_multi_byte() {
292 let s = IndexableStr::new("0😀2345678😀");
293 println!("length: {}", s.as_str().len());
294
295 assert_eq!(&s[..10], "0😀2345678😀");
296 }
297
298 #[test]
299 fn test_range_with_ending_index_too_large() {
300 let s = IndexableStr::new("0😀2345678😀");
301
302 let result = std::panic::catch_unwind(|| s.create_str_from_range(0, 11));
303 assert!(result.is_err());
304 }
305
306 #[test]
307 fn test_range_with_ending_index_is_less_than_the_starting_index() {
308 let s = IndexableStr::new("0😀2345678😀");
309
310 let result = std::panic::catch_unwind(|| s.create_str_from_range(20, 10));
311 assert!(result.is_err());
312 }
313}