string_view/lib.rs
1//! ## String View
2//!
3//! #### Work with views into string slices. Safely extend, reduce without losing parent string size.
4//!
5//! #### Use in-place modifications to speed up your code
6//!
7//! Example:
8//!
9//! ```rust
10//! let program_text = r#"
11//! fn main() {
12//! let text = "Hello World";
13//! }
14//! "#;
15//! use string_view::StrExt;
16//!
17//! let mut view = program_text.view_part(0, 0);
18//! view.extend_while(char::is_alphabetic);
19//! view.extend_while(|ch| ch != ' ');
20//! // view.reduce_left_while(char::is_alphabetic); todo!()
21//! assert_eq!(view.as_str(), "fn");
22//! ```
23
24#![no_std]
25
26use core::error::Error;
27use core::fmt::{Debug, Display};
28use core::ops::{Deref, DerefMut};
29
30pub struct StringView<'a> {
31 pub base: &'a str,
32 /// byte idx of view start inside [`StringView::base`].
33 view_start: usize,
34 /// byte len of view inside [`StringView::base`].
35 view_len: usize,
36}
37
38impl<'a> StringView<'a> {
39 /// Creates [`StringView`] of a whole [`str`] slice.
40 ///
41 /// see [`StringView::new_part`] to view part of [`str`] slice.
42 pub fn new(base: &'a str) -> Self {
43 Self {
44 base,
45 view_start: 0,
46 view_len: base.len(),
47 }
48 }
49
50 /// Creates [`StringView`] of a part of [`str`] slice using 2 byte indexes.
51 ///
52 /// ```rust
53 /// use string_view::StringView;
54 ///
55 /// let text = "Hello World";
56 /// let view = StringView::new_part(text, 6, 11);
57 ///
58 /// assert_eq!(view.as_str(), "World");
59 /// ```
60 ///
61 /// Or using [`StrExt`] extension trait:
62 ///
63 /// ```rust
64 /// use string_view::StrExt;
65 ///
66 /// let text = "Hello World";
67 /// let view = text.view_part(6, 11);
68 ///
69 /// assert_eq!(view.as_str(), "World");
70 /// ```
71 pub fn new_part(base: &'a str, view_start: usize, view_end: usize) -> Self {
72 assert!(
73 view_end >= view_start,
74 "View end index cannot be less then start index"
75 );
76 Self {
77 base,
78 view_start,
79 view_len: view_end - view_start,
80 }
81 }
82
83 /// Byte index of view start inside [`StringView::base`].
84 pub fn start(&self) -> usize {
85 self.view_start
86 }
87
88 /// Byte index of view end inside [`StringView::base`].
89 pub fn end(&self) -> usize {
90 self.view_start + self.view_len
91 }
92
93 pub fn as_str(&self) -> &str {
94 &self.base[self.view_start..self.view_start + self.view_len]
95 }
96
97 /// Extend string view to the right by `n` characters.
98 ///
99 /// panics if there is not enough characters in base string to the right of this view.
100 ///
101 /// see [`StringView::try_extend`] for fallible version.
102 ///
103 /// ```rust
104 /// use string_view::StrExt;
105 ///
106 /// let text = "Hello World";
107 /// let mut view = text.view_part(0, 5);
108 /// assert_eq!(view.as_str(), "Hello");
109 ///
110 /// view.extend(6);
111 /// assert_eq!(view.as_str(), "Hello World");
112 /// ```
113 pub fn extend(&mut self, n: usize) {
114 self.try_extend(n)
115 .expect("Unable to extend string view to the right")
116 }
117
118 /// Try to extend string view to the right by `n` characters.
119 ///
120 /// returns [`Err`] if there is not enough characters in base string to the right of this view.
121 ///
122 /// ```rust
123 /// use string_view::StrExt;
124 ///
125 /// let text = "Hello World";
126 ///
127 /// let mut view = text.view_part(0, 5);
128 /// assert_eq!(view.as_str(), "Hello");
129 ///
130 /// let err = view.try_extend(4);
131 /// assert!(err.is_ok());
132 /// assert_eq!(view.as_str(), "Hello Wor");
133 ///
134 /// let err = view.try_extend(10);
135 /// assert!(matches!(err, Err(BaseStringIsTooShort)));
136 /// assert_eq!(view.as_str(), "Hello Wor");
137 /// ```
138 pub fn try_extend(&mut self, n: usize) -> Result<(), BaseStringIsTooShort<RIGHT>> {
139 let mut combined_len = 0;
140 let mut char_iter = self.base[self.end()..].chars();
141 for _ in 0..n {
142 combined_len += char_iter.next().ok_or(BaseStringIsTooShort)?.len_utf8();
143 }
144 self.view_len += combined_len;
145 Ok(())
146 }
147
148 /// Extend string view to the right while `func` returns `true`.
149 ///
150 /// ```rust
151 /// use string_view::StrExt;
152 ///
153 /// let text = "Hello World !!!";
154 ///
155 /// let mut view = text.view_part(0, 2);
156 /// assert_eq!(view.as_str(), "He");
157 ///
158 /// view.extend_while(|ch| ch != ' ');
159 /// assert_eq!(view.as_str(), "Hello");
160 ///
161 /// view.extend(1);
162 /// view.extend_while(|ch| ch != ' ');
163 /// assert_eq!(view.as_str(), "Hello World");
164 /// ```
165 pub fn extend_while<F>(&mut self, mut func: F)
166 where
167 F: FnMut(char) -> bool,
168 {
169 let mut combined_len = 0;
170
171 for ch in self.base[self.end()..].chars() {
172 if func(ch) {
173 combined_len += ch.len_utf8();
174 }
175 else {
176 break;
177 }
178 }
179 self.view_len += combined_len;
180 }
181
182 /// Reduce string view from the right by `n` characters.
183 ///
184 /// panics if there is not enough characters in current string view.
185 ///
186 /// ```rust
187 /// use string_view::StrExt;
188 ///
189 /// let text = "Hello World";
190 ///
191 /// let mut view = text.view();
192 /// assert_eq!(view.as_str(), "Hello World");
193 ///
194 /// view.reduce(6);
195 /// assert_eq!(view.as_str(), "Hello");
196 /// ```
197 pub fn reduce(&mut self, n: usize) {
198 self.try_reduce(n)
199 .expect("Unable to reduce string view from the right")
200 }
201
202 /// Try to reduce string view from the right by `n` characters.
203 ///
204 /// returns [`Err`] if there is not enough characters in current string view.
205 ///
206 /// ```rust
207 /// use string_view::StrExt;
208 ///
209 /// let text = "One and only Hello World";
210 ///
211 /// let mut view = text.view_part(13, 24);
212 /// assert_eq!(view.as_str(), "Hello World");
213 ///
214 /// let result = view.try_reduce(6);
215 /// assert!(result.is_ok());
216 /// assert_eq!(view.as_str(), "Hello");
217 ///
218 /// let result = view.try_reduce(10);
219 /// assert!(matches!(result, Err(ViewIsTooShort)));
220 /// assert_eq!(view.as_str(), "Hello");
221 /// ```
222 pub fn try_reduce(&mut self, n: usize) -> Result<(), ViewIsTooShort<RIGHT>> {
223 let mut combined_len = 0;
224 let mut char_iter = self.base[self.start()..self.end()].chars().rev();
225 for _ in 0..n {
226 combined_len += char_iter.next().ok_or(ViewIsTooShort)?.len_utf8();
227 }
228 self.view_len -= combined_len;
229 Ok(())
230 }
231
232 /// Reduce string view from the right while `func` returns `true`.
233 ///
234 /// ```rust
235 /// use string_view::StrExt;
236 ///
237 /// let text = "Hello World !!!";
238 ///
239 /// let mut view = text.view();
240 /// assert_eq!(view.as_str(), "Hello World !!!");
241 ///
242 /// view.reduce_while(|ch| ch != ' ');
243 /// assert_eq!(view.as_str(), "Hello World ");
244 ///
245 /// view.reduce(1);
246 /// view.reduce_while(|ch| ch != ' ');
247 /// assert_eq!(view.as_str(), "Hello ");
248 /// ```
249 pub fn reduce_while<F>(&mut self, mut func: F)
250 where
251 F: FnMut(char) -> bool,
252 {
253 let mut combined_len = 0;
254 for ch in self.base[self.start()..self.end()].chars().rev() {
255 if func(ch) {
256 combined_len += ch.len_utf8();
257 }
258 else {
259 break;
260 }
261 }
262 self.view_len -= combined_len;
263 }
264}
265
266impl Deref for StringView<'_> {
267 type Target = str;
268
269 fn deref(&self) -> &Self::Target {
270 self.as_str()
271 }
272}
273
274impl Debug for StringView<'_> {
275 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
276 Debug::fmt(self.as_str(), f)
277 }
278}
279
280impl Display for StringView<'_> {
281 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
282 Display::fmt(self.as_str(), f)
283 }
284}
285
286type Side = bool;
287const RIGHT: bool = true;
288const _LEFT: bool = false;
289
290/// The only error case in [`StringView::try_extend`].
291pub struct BaseStringIsTooShort<const SIDE: Side>;
292
293impl<const SIDE: Side> Debug for BaseStringIsTooShort<SIDE> {
294 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
295 write!(
296 f,
297 "Base String contains less characters than `n` to the {} of the view",
298 if SIDE == RIGHT { "right" } else { "left" }
299 )
300 }
301}
302
303impl<const SIDE: Side> Display for BaseStringIsTooShort<SIDE> {
304 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
305 Debug::fmt(&self, f)
306 }
307}
308
309impl<const SIDE: bool> Error for BaseStringIsTooShort<SIDE> {}
310
311/// The only error case in [`StringView::try_reduce`].
312pub struct ViewIsTooShort<const SIDE: Side>;
313
314impl<const SIDE: Side> Debug for ViewIsTooShort<SIDE> {
315 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
316 write!(
317 f,
318 "View contains less characters than `n` to the {} of the view",
319 if SIDE == RIGHT { "right" } else { "left" }
320 )
321 }
322}
323
324impl<const SIDE: Side> Display for ViewIsTooShort<SIDE> {
325 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
326 Debug::fmt(&self, f)
327 }
328}
329
330impl<const SIDE: bool> Error for ViewIsTooShort<SIDE> {}
331
332/// In-place character representation inside str slice
333///
334/// Under the hood is &str with single character
335///
336/// ```rust
337/// use string_view::Char;
338///
339/// let ch = Char::new("A");
340/// ```
341///
342/// ```rust,should_panic
343/// use string_view::Char;
344///
345/// let ch = Char::new(""); // panics
346/// let ch = Char::new("Hello"); // panics
347/// ```
348pub struct Char<'a>(&'a str);
349
350impl Char<'_> {
351 pub fn new(ch: &str) -> Char<'_> {
352 let char_len = ch
353 .chars()
354 .next()
355 .expect("Unable to create Char from empty string")
356 .len_utf8();
357
358 assert_eq!(
359 char_len,
360 ch.len(),
361 "Char can only be constructed from single character string"
362 );
363
364 Char(ch)
365 }
366
367 pub fn char(&self) -> char {
368 self.0.chars().next().unwrap()
369 }
370
371 pub fn as_str(&self) -> &str {
372 self.0
373 }
374}
375
376impl<'a> Deref for Char<'a> {
377 type Target = str;
378
379 fn deref(&self) -> &str {
380 self.0
381 }
382}
383
384impl Debug for Char<'_> {
385 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
386 Debug::fmt(&self.0, f)
387 }
388}
389
390impl Display for Char<'_> {
391 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
392 Display::fmt(&self.0, f)
393 }
394}
395
396/// Iterator of chars in-place
397pub struct CharsInPlace<'a>(&'a str);
398
399impl<'a> Iterator for CharsInPlace<'a> {
400 type Item = Char<'a>;
401
402 fn next(&mut self) -> Option<Self::Item> {
403 let next_char_len = self.0.chars().next()?.len_utf8();
404
405 let (this, rest) = self.0.split_at(next_char_len);
406 self.0 = rest;
407
408 Some(Char(this))
409 }
410}
411
412/// In-place character representation inside mutable str slice
413///
414/// Convert to [`Char`] using [`CharMut::as_char`].
415pub struct CharMut<'a>(&'a mut str);
416
417impl CharMut<'_> {
418 pub fn new(ch: &mut str) -> CharMut<'_> {
419 let char_len = ch
420 .chars()
421 .next()
422 .expect("Unable to create CharMut from empty string")
423 .len_utf8();
424
425 assert_eq!(
426 char_len,
427 ch.len(),
428 "CharMut can only be constructed from single character string"
429 );
430
431 CharMut(ch)
432 }
433
434 pub fn char(&self) -> char {
435 self.0.chars().next().unwrap()
436 }
437
438 pub fn as_str(&self) -> &str {
439 self.0
440 }
441
442 pub fn as_str_mut(&mut self) -> &mut str {
443 self.0
444 }
445
446 pub fn as_char(&self) -> Char<'_> {
447 Char(&self.0)
448 }
449}
450
451impl<'a> Deref for CharMut<'a> {
452 type Target = str;
453
454 fn deref(&self) -> &str {
455 self.0
456 }
457}
458
459impl<'a> DerefMut for CharMut<'a> {
460 fn deref_mut(&mut self) -> &mut Self::Target {
461 self.0
462 }
463}
464
465impl Debug for CharMut<'_> {
466 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
467 Debug::fmt(&self.0, f)
468 }
469}
470
471impl Display for CharMut<'_> {
472 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
473 Display::fmt(&self.0, f)
474 }
475}
476
477/// Mutable iterator of chars in-place
478pub struct CharsInPlaceMut<'a>(&'a mut str);
479
480impl<'a> Iterator for CharsInPlaceMut<'a> {
481 type Item = CharMut<'a>;
482
483 fn next(&mut self) -> Option<Self::Item> {
484 let next_char_len = self.0.chars().next()?.len_utf8();
485
486 let this: &mut str = core::mem::take(&mut self.0);
487 let (this, rest) = this.split_at_mut(next_char_len);
488 self.0 = rest;
489
490 Some(CharMut(this))
491 }
492}
493
494pub trait StrExt {
495 fn view(&self) -> StringView<'_>;
496 fn view_part(&self, start_idx: usize, end_idx: usize) -> StringView<'_>;
497
498 /// Byte index of this [`Char`] start & end inside base [`str`].
499 ///
500 /// ```rust
501 /// use string_view::StrExt;
502 ///
503 /// let text = "Hello World";
504 /// let mut chars = text.chars_in_place();
505 ///
506 /// let first_char = chars.next().unwrap();
507 /// assert_eq!(first_char.as_str(), "H");
508 /// assert_eq!(text.char_idx(first_char), (0, 1));
509 ///
510 /// let second_char = chars.next().unwrap();
511 /// assert_eq!(second_char.as_str(), "e");
512 /// assert_eq!(text.char_idx(second_char), (1, 2));
513 /// ```
514 fn char_idx(&self, ch: Char) -> (usize, usize);
515
516 fn chars_in_place(&self) -> CharsInPlace<'_>;
517 fn chars_in_place_mut(&mut self) -> CharsInPlaceMut<'_>;
518}
519
520impl StrExt for str {
521 fn view(&self) -> StringView<'_> {
522 StringView::new(self)
523 }
524 fn view_part(&self, start_idx: usize, end_idx: usize) -> StringView<'_> {
525 StringView::new_part(self, start_idx, end_idx)
526 }
527 fn chars_in_place(&self) -> CharsInPlace<'_> {
528 CharsInPlace(self)
529 }
530 fn chars_in_place_mut(&mut self) -> CharsInPlaceMut<'_> {
531 CharsInPlaceMut(self)
532 }
533
534 /// Byte index of this [`Char`] start & end inside base [`str`].
535 ///
536 /// ```rust
537 /// use string_view::StrExt;
538 ///
539 /// let text = "Hello World";
540 /// let mut chars = text.chars_in_place();
541 ///
542 /// let first_char = chars.next().unwrap();
543 /// assert_eq!(first_char.as_str(), "H");
544 /// assert_eq!(text.char_idx(first_char), (0, 1));
545 ///
546 /// let second_char = chars.next().unwrap();
547 /// assert_eq!(second_char.as_str(), "e");
548 /// assert_eq!(text.char_idx(second_char), (1, 2));
549 /// ```
550 fn char_idx(&self, ch: Char) -> (usize, usize) {
551 let str_start = self.as_ptr() as usize;
552 let str_end = str_start + self.len();
553 let ch_start = ch.as_ptr() as usize;
554 let ch_end = ch_start + ch.len();
555
556 let range = str_start..str_end;
557 assert!(
558 range.contains(&ch_start),
559 "Char has to be inside this string to get its index"
560 );
561 assert!(
562 range.contains(&ch_end),
563 "Char has to be inside this string to get its index"
564 );
565
566 (ch_start - str_start, ch_end - str_start)
567 }
568}
569
570#[cfg(test)]
571mod test;