#![no_std]
use core::ops::{Bound, RangeBounds};
#[inline]
fn range_to_begin_end(range: impl RangeBounds<usize>) -> (usize, usize) {
let begin = match range.start_bound() {
Bound::Included(&b) => b,
Bound::Excluded(&b) => b + 1,
Bound::Unbounded => 0,
};
let end = match range.end_bound() {
Bound::Included(&b) => b + 1,
Bound::Excluded(&b) => b,
Bound::Unbounded => core::usize::MAX,
};
(begin, end)
}
pub trait StringSlice {
fn slice(&self, range: impl RangeBounds<usize>) -> &str;
fn try_slice(&self, range: impl RangeBounds<usize>) -> Option<&str>;
fn substring(&self, begin: usize, end: usize) -> &str;
fn try_substring(&self, begin: usize, end: usize) -> Option<&str>;
}
impl StringSlice for str {
#[inline]
fn slice(&self, range: impl RangeBounds<usize>) -> &str {
let (begin, end) = range_to_begin_end(range);
self.substring(begin, end)
}
#[inline]
fn try_slice(&self, range: impl RangeBounds<usize>) -> Option<&str> {
let (begin, end) = range_to_begin_end(range);
self.try_substring(begin, end)
}
#[inline]
fn substring(&self, begin: usize, end: usize) -> &str {
self.try_substring(begin, end)
.expect("begin < end when slicing string")
}
fn try_substring(&self, begin: usize, end: usize) -> Option<&str> {
if begin > end {
None
} else {
let mut ch_idx = self.char_indices().map(|(i, _c)| i);
let len = self.len();
let begin_ch = ch_idx.nth(begin).unwrap_or(len);
let end_ch = if end > begin {
ch_idx.nth(end - begin - 1).unwrap_or(len)
} else {
begin_ch
};
unsafe { Some(self.get_unchecked(begin_ch..end_ch)) }
}
}
}
#[cfg(test)]
mod tests {
use core::ops::Bound;
use super::StringSlice;
#[test]
fn test_utf8() {
let str = "🗻∈🌏";
assert_eq!("🗻", str.slice(0..1));
assert_eq!("∈", str.slice(1..2));
assert_eq!("🌏", str.slice(2..3));
}
#[test]
fn test_zero_len() {
let str = "test";
assert_eq!("", str.slice(0..0));
assert_eq!("", str.slice(..0));
assert_eq!("", str.slice(1..1));
}
#[test]
#[should_panic]
fn test_bad_range() {
"string".slice(4..1);
}
#[test]
fn test_try_bad_range() {
assert_eq!("string".try_slice(4..1), None);
}
#[test]
fn test_large_range() {
assert_eq!("test_string".slice(0..500), "test_string");
}
#[test]
fn test_range_types() {
assert_eq!("test_string".slice(..), "test_string");
assert_eq!("test_string".slice(5..), "string");
assert_eq!("test_string".slice(..8), "test_str");
assert_eq!("test_string".slice(5..8), "str");
assert_eq!("test_string".slice(5..=7), "str");
assert_eq!(
"test_string".slice((Bound::Excluded(4), Bound::Included(7))),
"str"
);
}
}