use std::cmp::Ordering;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::marker::PhantomData;
use std::ops::Range;
#[derive(Copy, Clone)]
pub struct StrView<'a> {
ptr: *const u8,
len: usize,
_marker: PhantomData<&'a str>,
}
unsafe impl<'a> Send for StrView<'a> {}
unsafe impl<'a> Sync for StrView<'a> {}
impl<'a> StrView<'a> {
#[inline(always)]
pub const fn new(s: &'a str) -> Self {
Self {
ptr: s.as_ptr(),
len: s.len(),
_marker: PhantomData,
}
}
#[inline(always)]
pub const fn empty() -> Self {
Self {
ptr: std::ptr::NonNull::dangling().as_ptr(),
len: 0,
_marker: PhantomData,
}
}
#[inline(always)]
pub const unsafe fn from_raw_parts(ptr: *const u8, len: usize) -> Self {
Self {
ptr,
len,
_marker: PhantomData,
}
}
#[inline(always)]
pub fn as_str(&self) -> &'a str {
unsafe { std::str::from_utf8_unchecked(std::slice::from_raw_parts(self.ptr, self.len)) }
}
#[inline(always)]
pub fn as_bytes(&self) -> &'a [u8] {
unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
}
#[inline(always)]
pub const fn len(&self) -> usize {
self.len
}
#[inline(always)]
pub const fn is_empty(&self) -> bool {
self.len == 0
}
#[inline]
pub fn slice(&self, range: Range<usize>) -> StrView<'a> {
let s = self.as_str();
StrView::new(&s[range])
}
#[inline]
pub fn try_slice(&self, range: Range<usize>) -> Option<StrView<'a>> {
if range.end > self.len {
return None;
}
let s = self.as_str();
s.get(range).map(StrView::new)
}
#[inline(always)]
pub unsafe fn slice_unchecked(&self, range: Range<usize>) -> StrView<'a> {
StrView {
ptr: self.ptr.add(range.start),
len: range.end - range.start,
_marker: PhantomData,
}
}
#[inline]
pub fn split_once(&self, c: char) -> Option<(StrView<'a>, StrView<'a>)> {
let s = self.as_str();
s.find(c).map(|pos| {
let (before, after) = s.split_at(pos);
(StrView::new(before), StrView::new(&after[c.len_utf8()..]))
})
}
#[inline]
pub fn strip_prefix(&self, prefix: &str) -> Option<StrView<'a>> {
self.as_str().strip_prefix(prefix).map(StrView::new)
}
#[inline]
pub fn strip_suffix(&self, suffix: &str) -> Option<StrView<'a>> {
self.as_str().strip_suffix(suffix).map(StrView::new)
}
#[inline]
pub fn trim(&self) -> StrView<'a> {
StrView::new(self.as_str().trim())
}
#[inline]
pub fn starts_with(&self, prefix: &str) -> bool {
self.as_str().starts_with(prefix)
}
#[inline]
pub fn ends_with(&self, suffix: &str) -> bool {
self.as_str().ends_with(suffix)
}
#[inline]
pub fn contains(&self, pattern: &str) -> bool {
self.as_str().contains(pattern)
}
#[inline]
pub fn find(&self, pattern: &str) -> Option<usize> {
self.as_str().find(pattern)
}
#[inline(always)]
pub const fn as_ptr(&self) -> *const u8 {
self.ptr
}
}
impl<'a> Default for StrView<'a> {
#[inline]
fn default() -> Self {
Self::empty()
}
}
impl<'a> fmt::Debug for StrView<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self.as_str(), f)
}
}
impl<'a> fmt::Display for StrView<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.as_str(), f)
}
}
impl<'a> PartialEq for StrView<'a> {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.as_str() == other.as_str()
}
}
impl<'a> Eq for StrView<'a> {}
impl<'a> PartialEq<str> for StrView<'a> {
#[inline]
fn eq(&self, other: &str) -> bool {
self.as_str() == other
}
}
impl<'a> PartialEq<&str> for StrView<'a> {
#[inline]
fn eq(&self, other: &&str) -> bool {
self.as_str() == *other
}
}
impl<'a> PartialEq<StrView<'a>> for str {
#[inline]
fn eq(&self, other: &StrView<'a>) -> bool {
self == other.as_str()
}
}
impl<'a> PartialOrd for StrView<'a> {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<'a> Ord for StrView<'a> {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
self.as_str().cmp(other.as_str())
}
}
impl<'a> Hash for StrView<'a> {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_str().hash(state)
}
}
impl<'a> AsRef<str> for StrView<'a> {
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl<'a> AsRef<[u8]> for StrView<'a> {
#[inline]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl<'a> From<&'a str> for StrView<'a> {
#[inline]
fn from(s: &'a str) -> Self {
Self::new(s)
}
}
impl<'a> From<&'a String> for StrView<'a> {
#[inline]
fn from(s: &'a String) -> Self {
Self::new(s.as_str())
}
}
pub struct Lines<'a> {
remaining: StrView<'a>,
}
impl<'a> Iterator for Lines<'a> {
type Item = StrView<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.remaining.is_empty() {
return None;
}
let s = self.remaining.as_str();
match s.find('\n') {
Some(pos) => {
let line = &s[..pos];
let rest = &s[pos + 1..];
self.remaining = StrView::new(rest);
let line = line.strip_suffix('\r').unwrap_or(line);
Some(StrView::new(line))
}
None => {
let line = self.remaining;
self.remaining = StrView::empty();
Some(line)
}
}
}
}
impl<'a> StrView<'a> {
#[inline]
pub fn lines(&self) -> Lines<'a> {
Lines { remaining: *self }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_strview_new() {
let s = "hello world";
let view = StrView::new(s);
assert_eq!(view.as_str(), "hello world");
assert_eq!(view.len(), 11);
assert!(!view.is_empty());
}
#[test]
fn test_strview_empty() {
let view = StrView::empty();
assert_eq!(view.as_str(), "");
assert_eq!(view.len(), 0);
assert!(view.is_empty());
}
#[test]
fn test_strview_slice() {
let s = "hello world";
let view = StrView::new(s);
let sub = view.slice(0..5);
assert_eq!(sub.as_str(), "hello");
}
#[test]
fn test_strview_try_slice() {
let view = StrView::new("hello");
assert!(view.try_slice(0..5).is_some());
assert!(view.try_slice(0..10).is_none());
}
#[test]
fn test_strview_split_once() {
let view = StrView::new("key=value");
let (before, after) = view.split_once('=').unwrap();
assert_eq!(before.as_str(), "key");
assert_eq!(after.as_str(), "value");
}
#[test]
fn test_strview_trim() {
let view = StrView::new(" hello ");
assert_eq!(view.trim().as_str(), "hello");
}
#[test]
fn test_strview_strip_prefix() {
let view = StrView::new("prefix_value");
let stripped = view.strip_prefix("prefix_").unwrap();
assert_eq!(stripped.as_str(), "value");
}
#[test]
fn test_strview_eq() {
let view = StrView::new("hello");
assert_eq!(view, "hello");
assert!(view == "hello");
}
#[test]
fn test_strview_hash() {
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(StrView::new("hello"));
assert!(set.contains(&StrView::new("hello")));
}
#[test]
fn test_strview_ord() {
let a = StrView::new("apple");
let b = StrView::new("banana");
assert!(a < b);
}
#[test]
fn test_strview_lines() {
let view = StrView::new("line1\nline2\nline3");
let lines: Vec<_> = view.lines().collect();
assert_eq!(lines.len(), 3);
assert_eq!(lines[0].as_str(), "line1");
assert_eq!(lines[1].as_str(), "line2");
assert_eq!(lines[2].as_str(), "line3");
}
#[test]
fn test_strview_lines_crlf() {
let view = StrView::new("line1\r\nline2\r\n");
let lines: Vec<_> = view.lines().collect();
assert_eq!(lines.len(), 2);
assert_eq!(lines[0].as_str(), "line1");
assert_eq!(lines[1].as_str(), "line2");
}
#[test]
fn test_strview_copy() {
let view = StrView::new("hello");
let copy = view; assert_eq!(view.as_str(), copy.as_str());
}
#[test]
fn test_strview_size() {
assert_eq!(std::mem::size_of::<StrView>(), 16);
}
#[test]
fn test_strview_from() {
let s = String::from("owned");
let view: StrView = (&s).into();
assert_eq!(view.as_str(), "owned");
let view2: StrView = "literal".into();
assert_eq!(view2.as_str(), "literal");
}
#[test]
fn test_strview_find() {
let view = StrView::new("hello world");
assert_eq!(view.find("world"), Some(6));
assert_eq!(view.find("xyz"), None);
}
#[test]
fn test_strview_contains() {
let view = StrView::new("hello world");
assert!(view.contains("world"));
assert!(!view.contains("xyz"));
}
}