//! Nul-terminated byte strings. use core::{marker::PhantomData, ptr::NonNull, str::Utf8Error}; use alloc::{borrow::Cow, string::String}; use crate::platform::types::{c_char, wchar_t}; mod private { pub trait Sealed {} } #[derive(Clone, Copy, Debug)] pub enum Thin {} #[derive(Clone, Copy, Debug)] pub enum Wide {} impl private::Sealed for Thin {} impl private::Sealed for Wide {} pub trait Kind: private::Sealed + Copy + 'static { /// c_char or wchar_t type C: Copy + 'static; // u8 or u32 type Char: Copy + From + Into + PartialEq + 'static; const NUL: Self::Char; const IS_THIN_NOT_WIDE: bool; fn r2c(c: Self::Char) -> Self::C; fn c2r(c: Self::C) -> Self::Char; fn chars_from_bytes(b: &[u8]) -> Option<&[Self::Char]>; fn chars_to_bytes(c: &[Self::Char]) -> Option<&[u8]>; unsafe fn strlen(s: *const Self::C) -> usize; unsafe fn strchr(s: *const Self::C, c: Self::C) -> *const Self::C; unsafe fn strchrnul(s: *const Self::C, c: Self::C) -> *const Self::C; } impl Kind for Thin { type C = c_char; type Char = u8; const NUL: Self::Char = 0; const IS_THIN_NOT_WIDE: bool = true; unsafe fn strlen(s: *const c_char) -> usize { unsafe { crate::header::string::strlen(s) } } unsafe fn strchr(s: *const c_char, c: c_char) -> *const c_char { unsafe { crate::header::string::strchr(s, c.into()) } } unsafe fn strchrnul(s: *const c_char, c: c_char) -> *const c_char { unsafe { crate::header::string::strchrnul(s, c.into()) } } fn r2c(c: u8) -> c_char { c as _ } fn c2r(c: c_char) -> u8 { c as _ } fn chars_from_bytes(b: &[u8]) -> Option<&[Self::Char]> { Some(b) } fn chars_to_bytes(c: &[Self::Char]) -> Option<&[u8]> { Some(c) } } impl Kind for Wide { type C = wchar_t; type Char = u32; const NUL: Self::Char = 0; const IS_THIN_NOT_WIDE: bool = false; unsafe fn strlen(s: *const Self::C) -> usize { unsafe { crate::header::wchar::wcslen(s) } } unsafe fn strchr(s: *const Self::C, c: Self::C) -> *const Self::C { unsafe { crate::header::wchar::wcschr(s, c) } } unsafe fn strchrnul(mut s: *const Self::C, c: Self::C) -> *const Self::C { // TODO: optimized function while unsafe { s.read() } != c && unsafe { s.read() } != 0 { s = unsafe { s.add(1) }; } s } fn r2c(c: Self::Char) -> Self::C { c as _ } fn c2r(c: Self::C) -> Self::Char { c as _ } fn chars_from_bytes(b: &[u8]) -> Option<&[Self::Char]> { None } fn chars_to_bytes(c: &[Self::Char]) -> Option<&[u8]> { None } } /// Safe wrapper for immutable borrowed C strings, guaranteed to be the same layout as `*const u8`. #[derive(Clone, Copy)] #[repr(transparent)] pub struct NulStr<'a, T: Kind> { ptr: NonNull, _marker: PhantomData<&'a [u8]>, } pub type CStr<'a> = NulStr<'a, Thin>; pub type WStr<'a> = NulStr<'a, Wide>; impl<'a, T: Kind> NulStr<'a, T> { /// Safety /// /// The ptr must be valid up to and including the first NUL byte from the base ptr. pub const unsafe fn from_ptr(ptr: *const T::C) -> Self { Self { ptr: unsafe { NonNull::new_unchecked(ptr.cast_mut()) }, _marker: PhantomData, } } pub unsafe fn from_nullable_ptr(ptr: *const T::C) -> Option { if ptr.is_null() { None } else { Some(unsafe { Self::from_ptr(ptr) }) } } /// Look for the closest occurence of `c`, and if found, split the string into a slice up to /// that byte and a `CStr` starting at that byte. #[inline] #[doc(alias = "strchrnul")] pub fn find_get_subslice_or_all( self, c: impl Into, ) -> Result<(&'a [T::Char], Self), (&'a [T::Char], Self)> { let c = c.into(); // SAFETY: strchrnul expects self.as_ptr() to be valid up to and including its last NUL // byte let found = unsafe { T::strchrnul(self.as_ptr(), T::r2c(c)) }; // SAFETY: the pointer returned from strchrnul is always a substring of this string, and // hence always valid as a CStr. let found = unsafe { Self::from_ptr(found) }; let until = unsafe { self.slice_until_substr(found) }; if found.first() == T::NUL { // The character was not found, and we got the end of the string instead. Err((until, found)) } else { Ok((until, found)) } } /// # Safety /// /// `substr` must be contained within `self` #[inline] pub unsafe fn slice_until_substr(self, substr: NulStr<'_, T>) -> &'a [T::Char] { let index = unsafe { // SAFETY: the sub-pointer as returned by strchr must be derived from the same // allocation substr.as_ptr().offset_from(self.as_ptr()) as usize }; unsafe { core::slice::from_raw_parts(self.as_ptr().cast::(), index) } } /// Look for the closest occurence of `c`, and if found, split the string into a slice up to /// that byte and a `CStr` starting at that byte. #[inline] pub fn find_get_subslice(self, c: T::Char) -> Option<(&'a [T::Char], Self)> { let rest = self.find(c)?; // SAFETY: the output of strchr is obviously a substring if it doesn't return NULL Some((unsafe { self.slice_until_substr(rest) }, rest)) } /// Look for the closest occurence of `c`, and return a new string starting at that byte if /// found. #[doc(alias = "strchr")] #[doc(alias = "wcschr")] #[inline] pub fn find(self, c: T::Char) -> Option { unsafe { // SAFETY: the only requirement is for self.as_ptr() to be valid up to and including // the nearest NUL byte, which this type requires let ret = T::strchr(self.as_ptr(), T::r2c(c)); // SAFETY: strchr must either return NULL (not found) or a substring of self, which can // never exceed the nearest NUL byte of self Self::from_nullable_ptr(ret) } } // TODO: strrchr, strchrnul wrappers #[inline] pub fn contains(self, c: T::Char) -> bool { self.find(c).is_some() } #[inline] pub fn first(self) -> T::Char { unsafe { // SAFETY: Self must be valid up to and including its nearest NUL byte, which certainly // implies its readable length is nonzero (string is empty if this first byte is 0). T::c2r(self.ptr.read()) } } #[inline] pub fn first_char(self) -> Option { char::from_u32(self.first().into()) } /// Same as `split_first` except also requires that the first char be convertible into `char` #[inline] pub fn split_first_char(self) -> Option<(char, Self)> { self.split_first() .and_then(|(c, r)| Some((char::from_u32(c.into())?, r))) } /// Split this string into `Some((first_byte, string_after_that))` or `None` if empty. #[inline] pub fn split_first(self) -> Option<(T::Char, Self)> { if self.first() == T::NUL { return None; } Some((self.first(), unsafe { Self::from_ptr(self.as_ptr().add(1)) })) } pub fn to_chars_with_nul(self) -> &'a [T::Char] { unsafe { // SAFETY: The string must be valid at least until (and including) the NUL byte. let len = T::strlen(self.ptr.as_ptr()); core::slice::from_raw_parts(self.ptr.as_ptr().cast(), len + 1) } } pub fn to_chars(self) -> &'a [T::Char] { let s = self.to_chars_with_nul(); &s[..s.len() - 1] } pub const fn as_ptr(self) -> *const T::C { self.ptr.as_ptr() } pub const unsafe fn from_chars_with_nul_unchecked(chars: &'a [T::Char]) -> Self { unsafe { Self::from_ptr(chars.as_ptr().cast()) } } pub fn from_chars_with_nul(chars: &'a [T::Char]) -> Result { if chars.last() != Some(&T::NUL) || chars[..chars.len() - 1].contains(&T::NUL) { return Err(FromCharsWithNulError); } Ok(unsafe { Self::from_chars_with_nul_unchecked(chars) }) } pub fn from_chars_until_nul(chars: &'a [T::Char]) -> Result { if !chars.contains(&T::NUL) { return Err(FromCharsUntilNulError); } Ok(unsafe { Self::from_chars_with_nul_unchecked(chars) }) } /// Scan the string to get its length. #[doc(alias = "strlen")] #[doc(alias = "wcslen")] pub fn len(self) -> usize { self.to_chars().len() } #[inline] pub fn is_empty(&self) -> bool { self.first() == T::NUL } } impl<'a> CStr<'a> { pub fn to_owned_cstring(self) -> CString { CString::from(unsafe { core::ffi::CStr::from_ptr(self.ptr.as_ptr()) }) } pub fn borrow(string: &'a CString) -> Self { unsafe { Self::from_ptr(string.as_ptr()) } } #[inline] pub fn to_bytes(self) -> &'a [u8] { self.to_chars() } #[inline] pub fn to_bytes_with_nul(self) -> &'a [u8] { self.to_chars_with_nul() } pub fn to_str(self) -> Result<&'a str, Utf8Error> { core::str::from_utf8(self.to_bytes()) } pub fn to_string_lossy(self) -> Cow<'a, str> { String::from_utf8_lossy(self.to_bytes()) } #[inline] pub const unsafe fn from_bytes_with_nul_unchecked(bytes: &'a [u8]) -> Self { unsafe { Self::from_chars_with_nul_unchecked(bytes) } } #[inline] pub fn from_bytes_with_nul(bytes: &'a [u8]) -> Result { Self::from_chars_with_nul(bytes) } #[inline] pub fn from_bytes_until_nul(bytes: &'a [u8]) -> Result { Self::from_chars_until_nul(bytes) } } unsafe impl Send for NulStr<'_, T> {} unsafe impl Sync for NulStr<'_, T> {} impl From<&core::ffi::CStr> for CStr<'_> { fn from(s: &core::ffi::CStr) -> Self { // SAFETY: // * We can assume that `s` is valid because the caller should have upheld its // safety concerns when constructing it. unsafe { Self::from_ptr(s.as_ptr()) } } } #[derive(Debug)] pub struct FromCharsWithNulError; #[derive(Debug)] pub struct FromCharsUntilNulError; pub use alloc::ffi::CString;