use core::{marker::PhantomPinned, mem, ptr, slice, str};
use munge::munge;
use rancor::{Panic, ResultExt as _, Source};
use crate::{
primitive::{ArchivedIsize, ArchivedUsize, FixedIsize, FixedUsize},
seal::Seal,
Place, Portable,
};
#[derive(Clone, Copy, Portable)]
#[rkyv(crate)]
#[repr(C)]
struct OutOfLineRepr {
len: ArchivedUsize,
offset: ArchivedIsize,
_phantom: PhantomPinned,
}
pub const INLINE_CAPACITY: usize = mem::size_of::<OutOfLineRepr>();
pub const OUT_OF_LINE_CAPACITY: usize = !(0b11 << (FixedUsize::BITS - 2));
#[derive(Clone, Copy, Portable)]
#[rkyv(crate)]
#[repr(C)]
struct InlineRepr {
bytes: [u8; INLINE_CAPACITY],
}
#[derive(Portable)]
#[rkyv(crate)]
#[repr(C)]
pub union ArchivedStringRepr {
out_of_line: OutOfLineRepr,
inline: InlineRepr,
}
impl ArchivedStringRepr {
#[inline]
pub fn is_inline(&self) -> bool {
unsafe { self.inline.bytes[0] & 0xc0 != 0x80 }
}
#[inline]
pub unsafe fn out_of_line_offset(&self) -> isize {
unsafe { self.out_of_line.offset.to_native() as isize }
}
#[inline]
pub fn as_ptr(&self) -> *const u8 {
if self.is_inline() {
unsafe { self.inline.bytes.as_ptr() }
} else {
unsafe {
(self as *const Self)
.cast::<u8>()
.offset(self.out_of_line_offset())
}
}
}
#[inline]
pub fn as_mut_ptr(this: Seal<'_, Self>) -> *mut u8 {
let this = unsafe { this.unseal_unchecked() };
if this.is_inline() {
unsafe { this.inline.bytes.as_mut_ptr() }
} else {
unsafe {
(this as *mut Self)
.cast::<u8>()
.offset(this.out_of_line_offset())
}
}
}
#[inline]
pub fn len(&self) -> usize {
if self.is_inline() {
unsafe {
self.inline
.bytes
.iter()
.position(|b| *b == 0xff)
.unwrap_or(INLINE_CAPACITY)
}
} else {
let len = unsafe { self.out_of_line.len.to_native() };
#[cfg(not(feature = "big_endian"))]
let len = (len & 0b0011_1111) | (len & !0xff) >> 2;
#[cfg(feature = "big_endian")]
let len = len & (FixedUsize::MAX >> 2);
len as usize
}
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[inline]
pub fn as_str_ptr(&self) -> *const str {
ptr_meta::from_raw_parts(self.as_ptr().cast(), self.len())
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self.as_ptr(), self.len()) }
}
#[inline]
pub fn as_bytes_seal(this: Seal<'_, Self>) -> Seal<'_, [u8]> {
let len = this.len();
let slice =
unsafe { slice::from_raw_parts_mut(Self::as_mut_ptr(this), len) };
Seal::new(slice)
}
#[inline]
pub fn as_str(&self) -> &str {
unsafe { str::from_utf8_unchecked(self.as_bytes()) }
}
#[inline]
pub fn as_str_seal(this: Seal<'_, Self>) -> Seal<'_, str> {
let bytes =
unsafe { Seal::unseal_unchecked(Self::as_bytes_seal(this)) };
Seal::new(unsafe { str::from_utf8_unchecked_mut(bytes) })
}
#[inline]
pub unsafe fn emplace_inline(value: &str, out: *mut Self) {
debug_assert!(value.len() <= INLINE_CAPACITY);
let out_bytes = unsafe { ptr::addr_of_mut!((*out).inline.bytes) };
unsafe {
for i in 0..value.len() {
out_bytes.cast::<u8>().add(i).write(value.as_bytes()[i]);
}
for i in value.len()..INLINE_CAPACITY {
out_bytes.cast::<u8>().add(i).write(0xff);
}
}
}
pub unsafe fn try_emplace_out_of_line<E: Source>(
value: &str,
target: usize,
out: Place<Self>,
) -> Result<(), E> {
munge! {
let ArchivedStringRepr {
out_of_line: OutOfLineRepr { len, offset, _phantom: _ }
} = out;
}
let l = value.len() as FixedUsize;
#[cfg(not(feature = "big_endian"))]
let l = (l & 0x3f) | 0b1000_0000 | (l & !0b0011_1111) << 2;
#[cfg(feature = "big_endian")]
let l = l & (FixedUsize::MAX >> 2) | (1 << FixedUsize::BITS - 1);
len.write(ArchivedUsize::from_native(l));
let off = crate::rel_ptr::signed_offset(out.pos(), target)?;
offset.write(ArchivedIsize::from_native(off as FixedIsize));
Ok(())
}
#[inline]
pub unsafe fn emplace_out_of_line(
value: &str,
target: usize,
out: Place<Self>,
) {
unsafe {
Self::try_emplace_out_of_line::<Panic>(value, target, out)
.always_ok()
}
}
}
#[cfg(feature = "bytecheck")]
const _: () = {
use core::{error::Error, fmt};
use bytecheck::{rancor::Fallible, CheckBytes};
use rancor::fail;
#[derive(Debug)]
pub struct CheckStringReprError;
impl fmt::Display for CheckStringReprError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"String representation was out-of-line but the length was too \
short",
)
}
}
impl Error for CheckStringReprError {}
unsafe impl<C> CheckBytes<C> for ArchivedStringRepr
where
C: Fallible + ?Sized,
C::Error: Source,
{
unsafe fn check_bytes(
value: *const Self,
_: &mut C,
) -> Result<(), C::Error> {
let repr = unsafe { &*value };
if !repr.is_inline() && repr.len() <= INLINE_CAPACITY {
fail!(CheckStringReprError);
} else {
Ok(())
}
}
}
};