use crate::{C8Str, C8StrError, NonZeroChar};
use alloc::vec; use alloc::{borrow::Cow, boxed::Box, ffi::CString, string::String, vec::Vec};
use core::hint::unreachable_unchecked;
use core::{
borrow::Borrow,
ffi::CStr,
fmt::{self, Display},
mem::size_of,
ops::Deref,
};
mod sealed_string_type {
pub trait Sealed {}
}
pub trait StringType: sealed_string_type::Sealed + Into<String> {
fn as_bytes(&self) -> &[u8];
fn is_allocated(&self) -> bool;
fn is_empty(&self) -> bool;
fn len(&self) -> usize;
}
impl sealed_string_type::Sealed for String {}
impl sealed_string_type::Sealed for &str {}
impl sealed_string_type::Sealed for Box<str> {}
impl sealed_string_type::Sealed for Cow<'_, str> {}
impl StringType for String {
#[inline(always)]
fn as_bytes(&self) -> &[u8] {
self.as_bytes()
}
#[inline(always)]
fn is_allocated(&self) -> bool {
true
}
#[inline(always)]
fn is_empty(&self) -> bool {
self.is_empty()
}
#[inline(always)]
fn len(&self) -> usize {
self.len()
}
}
impl StringType for &str {
#[inline(always)]
fn as_bytes(&self) -> &[u8] {
(*self).as_bytes()
}
#[inline(always)]
fn is_allocated(&self) -> bool {
false
}
#[inline(always)]
fn is_empty(&self) -> bool {
(*self).is_empty()
}
#[inline(always)]
fn len(&self) -> usize {
(*self).len()
}
}
impl StringType for Box<str> {
#[inline(always)]
fn as_bytes(&self) -> &[u8] {
(**self).as_bytes()
}
#[inline(always)]
fn is_allocated(&self) -> bool {
true
}
#[inline(always)]
fn is_empty(&self) -> bool {
(**self).is_empty()
}
#[inline(always)]
fn len(&self) -> usize {
(**self).len()
}
}
impl StringType for Cow<'_, str> {
#[inline(always)]
fn as_bytes(&self) -> &[u8] {
(**self).as_bytes()
}
#[inline(always)]
fn is_allocated(&self) -> bool {
matches!(self, Self::Owned(_))
}
#[inline(always)]
fn is_empty(&self) -> bool {
(**self).is_empty()
}
#[inline(always)]
fn len(&self) -> usize {
(**self).len()
}
}
const EMPTY_C8STR: &C8Str = c8!("");
#[repr(transparent)]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct C8String(Option<String>);
const _: () = assert!(size_of::<Option<C8String>>() == size_of::<String>());
impl C8String {
#[cfg(test)]
pub(crate) fn inner_capacity(&self) -> usize {
match &self.0 {
Some(s) => s.capacity(),
None => 0,
}
}
#[inline(always)]
pub const fn new() -> C8String {
Self(None)
}
pub fn from_vec(vec: Vec<u8>) -> Result<C8String, C8StrError> {
if vec.is_empty() {
return Ok(Self(None));
}
match CString::new(vec) {
Ok(str) => match String::from_utf8(str.into_bytes_with_nul()) {
Ok(str) => Ok(Self(Some(str))),
Err(e) => Err(C8StrError::not_utf8(e.utf8_error().valid_up_to())),
},
Err(e) => Err(C8StrError::inner_zero(e.nul_position())),
}
}
pub fn from_vec_with_nul(vec: Vec<u8>) -> Result<C8String, C8StrError> {
if vec.last() != Some(&0) {
return Err(C8StrError::missing_terminator());
}
match CString::from_vec_with_nul(vec) {
Ok(str) => match String::from_utf8(str.into_bytes_with_nul()) {
Ok(str) => Ok(Self(Some(str))),
Err(e) => Err(C8StrError::not_utf8(e.utf8_error().valid_up_to())),
},
Err(_) => Err(C8StrError::inner_zero_unknown()),
}
}
pub fn from_string(str: impl StringType) -> Result<C8String, C8StrError> {
if str.is_empty() {
Ok(Self(None))
} else {
let mut str = str.into();
str.push('\0');
Self::from_string_with_nul(str)
}
}
pub fn from_string_with_nul(str: impl StringType) -> Result<C8String, C8StrError> {
if !str.is_allocated() && str.len() == 1 && str.as_bytes()[0] == 0 {
return Ok(Self(None));
}
let str = str.into();
C8Str::from_str_with_nul(&str)?;
Ok(Self(Some(str)))
}
pub fn from_c_string(str: CString) -> Result<C8String, C8StrError> {
let bytes = str.into_bytes_with_nul();
Ok(Self(Some(String::from_utf8(bytes).map_err(|s| {
C8StrError::not_utf8(s.utf8_error().valid_up_to())
})?)))
}
pub fn as_c8_str(&self) -> &C8Str {
match &self.0 {
Some(s) => unsafe {
C8Str::from_str_with_nul_unchecked(s.as_str())
},
None => EMPTY_C8STR,
}
}
pub fn into_boxed_c8_str(self) -> Box<C8Str> {
let boxed = match self.0 {
Some(s) => s,
None => String::from("\0"),
}
.into_boxed_str();
unsafe {
Box::from_raw(Box::into_raw(boxed) as *mut C8Str)
}
}
pub fn into_bytes(self) -> Vec<u8> {
match self.0 {
Some(mut s) => {
s.pop(); s.into_bytes()
}
None => Vec::new(),
}
}
pub fn into_bytes_with_nul(self) -> Vec<u8> {
match self.0 {
Some(s) => s.into_bytes(),
None => vec![0],
}
}
pub fn into_string(self) -> String {
match self.0 {
Some(mut s) => {
s.pop();
s
}
None => String::new(),
}
}
pub fn into_string_with_nul(self) -> String {
match self.0 {
Some(s) => s,
None => String::from("\0"),
}
}
pub fn into_c_string(self) -> CString {
unsafe { CString::from_vec_with_nul_unchecked(self.into_bytes_with_nul()) }
}
pub fn reserve(&mut self, additional: usize) {
match &mut self.0 {
Some(s) => s.reserve(additional),
None => {
let mut s = String::with_capacity(additional + 1);
s.push('\0');
self.0 = Some(s);
}
}
}
pub fn reserve_exact(&mut self, additional: usize) {
match &mut self.0 {
Some(s) => s.reserve_exact(additional),
None => {
let mut s = String::with_capacity(additional + 1);
s.push('\0');
self.0 = Some(s);
}
}
}
pub fn push(&mut self, ch: NonZeroChar) {
self.reserve(1); let Some(s) = &mut self.0 else {
unsafe {
unreachable_unchecked()
}
};
s.pop();
s.push(ch.get());
s.push('\0');
}
pub fn push_c8_str(&mut self, c8_str: &C8Str) {
self.reserve(c8_str.len()); let Some(s) = &mut self.0 else {
unsafe {
unreachable_unchecked()
}
};
s.pop();
s.push_str(c8_str.as_str_with_nul());
}
pub fn push_c_str(&mut self, c_str: &CStr) -> Result<(), C8StrError> {
let str = match c_str.to_str() {
Ok(str) => str,
Err(e) => return Err(C8StrError::not_utf8(e.valid_up_to())),
};
self.reserve(str.len()); let Some(s) = &mut self.0 else {
unsafe {
unreachable_unchecked()
}
};
s.pop();
s.push_str(str);
s.push('\0');
Ok(())
}
pub fn push_str(&mut self, str: &str) -> Result<(), C8StrError> {
if let Some(i) = str.as_bytes().iter().position(|&b| b == 0) {
return Err(C8StrError::inner_zero(i));
}
self.reserve(str.len()); let Some(s) = &mut self.0 else {
unsafe {
unreachable_unchecked()
}
};
s.pop();
s.push_str(str);
s.push('\0');
Ok(())
}
pub fn pop(&mut self) -> Option<NonZeroChar> {
match &mut self.0 {
Some(s) => {
s.pop(); let popped = s.pop().map(|c| unsafe {
NonZeroChar::new_unchecked(c)
});
s.push('\0');
popped
}
None => None,
}
}
pub fn clear(&mut self) {
if let Some(s) = &mut self.0 {
s.clear();
s.push('\0');
}
}
}
impl AsRef<C8Str> for C8String {
#[inline(always)]
fn as_ref(&self) -> &C8Str {
self.as_c8_str()
}
}
impl AsRef<CStr> for C8String {
#[inline(always)]
fn as_ref(&self) -> &CStr {
self.as_c_str()
}
}
impl AsRef<str> for C8String {
#[inline(always)]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl Borrow<C8Str> for C8String {
#[inline(always)]
fn borrow(&self) -> &C8Str {
self.as_c8_str()
}
}
impl Default for C8String {
#[inline(always)]
fn default() -> Self {
Self(None)
}
}
impl Deref for C8String {
type Target = C8Str;
#[inline(always)]
fn deref(&self) -> &Self::Target {
self.as_c8_str()
}
}
impl Display for C8String {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.as_str().fmt(f)
}
}
impl From<&C8Str> for C8String {
fn from(value: &C8Str) -> Self {
if value.is_empty() {
Self(None)
} else {
Self(Some(String::from(value.as_str_with_nul())))
}
}
}
impl From<Box<C8Str>> for C8String {
fn from(value: Box<C8Str>) -> Self {
if value.is_empty() {
Self(None)
} else {
Self(Some(String::from(unsafe {
Box::from_raw(Box::into_raw(value) as *mut str)
})))
}
}
}
impl TryFrom<String> for C8String {
type Error = C8StrError;
#[inline(always)]
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::from_string(value)
}
}
impl From<C8String> for String {
#[inline(always)]
fn from(value: C8String) -> Self {
value.into_string()
}
}
impl TryFrom<CString> for C8String {
type Error = C8StrError;
#[inline(always)]
fn try_from(value: CString) -> Result<Self, Self::Error> {
Self::from_c_string(value)
}
}
impl From<C8String> for CString {
#[inline(always)]
fn from(value: C8String) -> Self {
value.into_c_string()
}
}