diff --git a/Cargo.toml b/Cargo.toml index a3ff6f6..9f2a61e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,12 +4,12 @@ version = "0.3.0" rust-version = "1.73" # also change in ci.yml authors = ["Laurenz "] edition = "2021" -description = "Compact, clone-on-write vector and string." +description = "Compact, clone-on-write vector, string, and byte buffer." repository = "https://github.com/typst/ecow" readme = "README.md" license = "MIT OR Apache-2.0" categories = ["data-structures", "no-std"] -keywords = ["string", "vector", "sso", "cow"] +keywords = ["string", "vector", "bytes", "sso", "cow"] [features] default = ["std"] diff --git a/README.md b/README.md index 609cca2..85fc0eb 100644 --- a/README.md +++ b/README.md @@ -2,17 +2,20 @@ [![Crates.io](https://img.shields.io/crates/v/ecow.svg)](https://crates.io/crates/ecow) [![Documentation](https://docs.rs/ecow/badge.svg)](https://docs.rs/ecow) -Compact, clone-on-write vector and string. +Compact, clone-on-write vector, string, and byte buffer. ## Types -- An `EcoVec` is a reference-counted clone-on-write vector. It takes up two - words of space (= 2 usize) and has the same memory layout as a `&[T]` slice. - Within its allocation, it stores a reference count, its capacity and its - elements. - -- An `EcoString` is a reference-counted clone-on-write string with inline - storage. It takes up 16 bytes of space. It has 15 bytes of inline storage and - starting from 16 bytes it becomes an `EcoVec`. +- `EcoVec` is a reference-counted clone-on-write vector. It takes up two words + of space (= 2 usize) and has the same memory layout as a `&[T]` slice. Within + its allocation, it stores a reference count, its capacity and its elements. + +- `EcoString` is a reference-counted clone-on-write string with inline storage. + It takes up 16 bytes of space. It has 15 bytes of inline storage and starting + from 16 bytes it becomes an `EcoVec`. + +- `EcoBytes` is a reference-counted clone-on-write byte buffer with inline + storage. Like an `EcoString`, it takes up 16 bytes and stores up to 15 bytes + inline before spilling to an `EcoVec`. ## Example ```rust diff --git a/src/bytes.rs b/src/bytes.rs new file mode 100644 index 0000000..2d7ad5c --- /dev/null +++ b/src/bytes.rs @@ -0,0 +1,1044 @@ +//! A clone-on-write byte buffer with inline storage. + +use alloc::borrow::Cow; +use alloc::vec::Vec; +use core::borrow::Borrow; +use core::cmp::Ordering; +use core::fmt::{self, Debug, Formatter}; +use core::hash::{Hash, Hasher}; +use core::mem::{self, ManuallyDrop}; +use core::ops::{Deref, RangeBounds}; +use core::ptr; + +use super::EcoVec; + +/// An economical byte buffer with inline storage and clone-on-write semantics. +/// +/// This type has a size of 16 bytes. It has 15 bytes of inline storage and, +/// starting at 16 bytes, spills into an [`EcoVec`]. The internal reference +/// counter of the heap variant is atomic, making this type [`Sync`] and +/// [`Send`]. +/// +/// # Example +/// ``` +/// use ecow::EcoBytes; +/// +/// // This is stored inline. +/// let small = EcoBytes::from(b"Welcome"); +/// +/// // This spills to the heap. The clone shares its allocation until mutation. +/// let mut big = small.repeat(3); +/// let clone = big.clone(); +/// big.push(b'!'); +/// assert_ne!(big, clone); +/// ``` +/// +/// # Note +/// The above holds true for normal 32-bit or 64-bit little-endian systems. On +/// 64-bit big-endian systems, the type's size increases to 24 bytes and the +/// amount of inline storage to 23 bytes. +pub struct EcoBytes(Repr); + +/// The internal representation. +/// +/// On 64-bit little endian, this assumes that no valid EcoVec exists in which +/// the highest-order bit of the InlineVec's `tagged_len` would be set. This is +/// true because EcoVec is repr(C) and its second field `len` is bounded by +/// `isize::MAX`. On 32-bit, it's no problem and for 64-bit big endian, we +/// have an increased limit to prevent the overlap. +#[repr(C)] +union Repr { + inline: InlineVec, + spilled: ManuallyDrop>, +} + +/// This is never stored in memory, it's just an abstraction for safe access. +#[derive(Debug)] +enum Variant<'a> { + Inline(&'a InlineVec), + Spilled(&'a EcoVec), +} + +/// This is never stored in memory, it's just an abstraction for safe access. +#[derive(Debug)] +enum VariantMut<'a> { + Inline(&'a mut InlineVec), + Spilled(&'a mut EcoVec), +} + +/// The maximum amount of inline storage. Typically, this is 15 bytes. +/// +/// However, in the rare exotic system, we still want things to be safe. +/// Therefore, the following special cases: +/// - For big endian, we increase the limit such that the tagged length of the +/// inline variants doesn't overlap with the EcoVec. For little endian, it's +/// fine since the highest order bit is never set for a valid EcoVec. +/// - In case somehow EcoVec is very big (128-bit pointers woah), increase the +/// limit too. +pub(crate) const LIMIT: usize = { + let mut limit = 15; + if limit < mem::size_of::>() - 1 { + limit = mem::size_of::>() - 1; + } + if cfg!(target_endian = "big") { + limit += mem::size_of::(); + } + limit +}; + +/// This bit is used to check whether we are inline or not. On 64-bit little +/// endian, it coincides with the highest-order bit of an EcoVec's length, which +/// can't be set because the EcoVec's length never exceeds `isize::MAX`. +const LEN_TAG: u8 = 0b1000_0000; + +/// This is used to mask off the tag to get the inline variant's length. +const LEN_MASK: u8 = 0b0111_1111; + +impl EcoBytes { + /// Maximum number of bytes for an inline `EcoBytes` before spilling to the + /// heap. + /// + /// The exact value for this is architecture dependent. + /// + /// # Note + /// This value is semver exempt and can be changed with any update. + pub const INLINE_LIMIT: usize = LIMIT; + + /// Create a new, empty byte buffer. + /// + /// This does not allocate. + #[inline] + pub const fn new() -> Self { + Self::from_inline(InlineVec::new()) + } + + /// Create a new, inline byte buffer. + /// + /// Panics if the slice's length exceeds the capacity of the inline storage. + #[inline] + pub const fn inline(bytes: &[u8]) -> Self { + let Ok(inline) = InlineVec::from_slice(bytes) else { + exceeded_inline_capacity(); + }; + Self::from_inline(inline) + } + + /// Try to create a new, inline byte buffer. + /// + /// Returns `None` if the slice's length exceeds the capacity of the inline + /// storage. + #[inline] + pub const fn try_inline(bytes: &[u8]) -> Option { + match InlineVec::from_slice(bytes) { + Ok(inline) => Some(Self::from_inline(inline)), + Err(()) => None, + } + } + + #[inline] + pub(crate) const fn from_inline(inline: InlineVec) -> Self { + Self(Repr { inline }) + } + + #[inline] + const fn from_eco(vec: EcoVec) -> Self { + // Safety: + // Explicitly set `tagged_len` to 0 to mark this as a spilled variant. + // Just initializing with `Repr { spilled: ... }` would leave + // `tagged_len` uninitialized, leading to undefined behaviour on access. + let mut repr = Repr { + inline: InlineVec { buf: [0; LIMIT], tagged_len: 0 }, + }; + repr.spilled = ManuallyDrop::new(vec); + Self(repr) + } + + /// Create a new, empty byte buffer with at least the specified capacity. + #[inline] + pub fn with_capacity(capacity: usize) -> Self { + if capacity <= LIMIT { + Self::new() + } else { + Self::from_eco(EcoVec::with_capacity(capacity)) + } + } + + /// Returns `true` if the buffer contains no bytes. + #[inline] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// The number of bytes in the buffer. + #[inline] + pub fn len(&self) -> usize { + match self.variant() { + Variant::Inline(inline) => inline.len(), + Variant::Spilled(spilled) => spilled.len(), + } + } + + /// How many bytes the buffer can hold without allocating. + /// + /// If the buffer's heap allocation is shared, mutation can still allocate + /// even when the requested length fits within this capacity. + #[inline] + pub fn capacity(&self) -> usize { + match self.variant() { + Variant::Inline(_) => LIMIT, + Variant::Spilled(spilled) => spilled.capacity(), + } + } + + /// Extracts a slice containing the entire buffer. + #[inline] + pub fn as_slice(&self) -> &[u8] { + match self.variant() { + Variant::Inline(inline) => inline.as_slice(), + Variant::Spilled(spilled) => spilled.as_slice(), + } + } + + /// Produce a mutable slice containing the entire buffer. + /// + /// Clones the buffer if its reference count is larger than 1. + #[inline] + pub fn make_mut(&mut self) -> &mut [u8] { + match self.variant_mut() { + VariantMut::Inline(inline) => inline.as_mut_slice(), + VariantMut::Spilled(spilled) => spilled.make_mut(), + } + } + + /// Add a byte at the end of the buffer. + /// + /// Clones the buffer if its reference count is larger than 1. + #[inline] + pub fn push(&mut self, byte: u8) { + match self.variant_mut() { + VariantMut::Inline(inline) => { + if inline.push(byte).is_err() { + let capacity = EcoVec::::amortized_cap(inline.len(), 1, LIMIT); + let mut eco = EcoVec::with_capacity(capacity); + eco.extend_from_byte_slice(inline.as_slice()); + eco.push(byte); + *self = Self::from_eco(eco); + } + } + VariantMut::Spilled(spilled) => { + spilled.push(byte); + } + } + } + + /// Removes and returns the last byte, or returns `None` if the buffer is + /// empty. + /// + /// Clones the buffer if its reference count is larger than 1. + #[inline] + pub fn pop(&mut self) -> Option { + match self.variant_mut() { + VariantMut::Inline(inline) => inline.pop(), + VariantMut::Spilled(spilled) => spilled.pop(), + } + } + + /// Inserts a byte at an index within the buffer, shifting all bytes after it + /// to the right. + /// + /// Clones the buffer if its reference count is larger than 1. + /// + /// Panics if `index > len`. + pub fn insert(&mut self, index: usize, byte: u8) { + match self.variant_mut() { + VariantMut::Inline(inline) => { + if inline.insert(index, byte).is_err() { + let capacity = EcoVec::::amortized_cap(inline.len(), 1, LIMIT); + let mut eco = EcoVec::with_capacity(capacity); + eco.extend_from_byte_slice(inline.as_slice()); + eco.insert(index, byte); + *self = Self::from_eco(eco); + } + } + VariantMut::Spilled(spilled) => spilled.insert(index, byte), + } + } + + /// Removes and returns the byte at position index within the buffer, + /// shifting all bytes after it to the left. + /// + /// Clones the buffer if its reference count is larger than 1. + /// + /// Panics if `index >= len`. + pub fn remove(&mut self, index: usize) -> u8 { + match self.variant_mut() { + VariantMut::Inline(inline) => inline.remove(index), + VariantMut::Spilled(spilled) => spilled.remove(index), + } + } + + /// Copies and pushes all bytes in a slice to the buffer. + #[inline] + pub fn extend_from_slice(&mut self, bytes: &[u8]) { + if bytes.is_empty() { + return; + } + + match self.variant_mut() { + VariantMut::Inline(inline) => { + if inline.extend_from_slice(bytes).is_err() { + let needed = inline.len() + bytes.len(); + let mut eco = EcoVec::with_capacity(needed.next_power_of_two()); + eco.extend_from_byte_slice(inline.as_slice()); + eco.extend_from_byte_slice(bytes); + *self = Self::from_eco(eco); + } + } + VariantMut::Spilled(spilled) => { + spilled.extend_from_byte_slice(bytes); + } + } + } + + /// Inserts the given byte slice at the `index`. + /// + /// Clones the buffer if its reference count is larger than 1. + #[inline] + pub fn insert_slice(&mut self, index: usize, bytes: &[u8]) { + match self.variant_mut() { + VariantMut::Inline(inline) => { + if inline.insert_slice(index, bytes).is_err() { + let needed = inline.len() + bytes.len(); + let mut eco = EcoVec::with_capacity(needed.next_power_of_two()); + let (a, b) = inline.as_slice().split_at(index); + eco.extend_from_byte_slice(a); + eco.extend_from_byte_slice(bytes); + eco.extend_from_byte_slice(b); + *self = Self::from_eco(eco); + } + } + VariantMut::Spilled(spilled) => { + spilled.splice(index..index, bytes.iter().copied()); + } + } + } + + /// Removes all bytes from the buffer. + #[inline] + pub fn clear(&mut self) { + match self.variant_mut() { + VariantMut::Inline(inline) => inline.clear(), + VariantMut::Spilled(spilled) => spilled.clear(), + } + } + + /// Shortens the buffer, keeping the first `target` bytes and dropping the + /// rest. + /// + /// Clones the buffer if its reference count is larger than 1 and + /// `target < len`. + #[inline] + pub fn truncate(&mut self, target: usize) { + match self.variant_mut() { + VariantMut::Inline(inline) => inline.truncate(target), + VariantMut::Spilled(spilled) => spilled.truncate(target), + } + } + + /// Reserve space for at least `additional` more bytes. + /// + /// Guarantees that the resulting buffer has space for `additional` more + /// bytes and, if spilled, uniquely owns its backing allocation. + pub fn reserve(&mut self, additional: usize) { + match self.variant_mut() { + VariantMut::Inline(inline) => { + if additional > LIMIT - inline.len() { + let capacity = + EcoVec::::amortized_cap(inline.len(), additional, LIMIT); + let mut eco = EcoVec::with_capacity(capacity); + eco.extend_from_byte_slice(inline.as_slice()); + *self = Self::from_eco(eco); + } + } + VariantMut::Spilled(spilled) => spilled.reserve(additional), + } + } + + /// Repeat this byte buffer `n` times. + pub fn repeat(&self, n: usize) -> Self { + let capacity = self.len().saturating_mul(n); + let mut bytes = Self::with_capacity(capacity); + for _ in 0..n { + bytes.extend_from_slice(self); + } + bytes + } + + #[inline] + pub(crate) fn remove_range(&mut self, range: R) + where + R: RangeBounds, + { + match self.variant_mut() { + VariantMut::Inline(inline) => inline.remove_range(range), + VariantMut::Spilled(spilled) => { + spilled.drain(range); + } + } + } + + /// Whether this byte buffer is stored inline. + // If this returns true, guarantees that `self.0.inline` is initialized. + // Otherwise, guarantees that `self.0.spilled` is initialized. + #[inline] + pub fn is_inline(&self) -> bool { + // Safety: + // We always initialize tagged_len, even for the `EcoVec` variant. For + // the inline variant the highest-order bit is always `1`. For the + // spilled variant, it is initialized with `0` and cannot deviate from + // that because the EcoVec's `len` field is bounded by `isize::MAX`. (At + // least on 64-bit little endian; on 32-bit or big-endian the EcoVec + // and tagged_len fields don't even overlap, meaning tagged_len stays at + // its initial value.) + unsafe { self.0.inline.tagged_len & LEN_TAG != 0 } + } + + #[inline] + fn variant(&self) -> Variant<'_> { + unsafe { + // Safety: + // We access the respective variant only if the check passes. + if self.is_inline() { + Variant::Inline(&self.0.inline) + } else { + Variant::Spilled(&self.0.spilled) + } + } + } + + #[inline] + fn variant_mut(&mut self) -> VariantMut<'_> { + unsafe { + // Safety: + // We access the respective variant only if the check passes. + if self.is_inline() { + VariantMut::Inline(&mut self.0.inline) + } else { + VariantMut::Spilled(&mut self.0.spilled) + } + } + } +} + +impl Clone for EcoBytes { + #[inline] + fn clone(&self) -> Self { + match self.variant() { + Variant::Inline(inline) => Self::from_inline(*inline), + Variant::Spilled(spilled) => Self::from_eco(spilled.clone()), + } + } +} + +impl Drop for EcoBytes { + #[inline] + fn drop(&mut self) { + if let VariantMut::Spilled(spilled) = self.variant_mut() { + unsafe { + // Safety: We are guaranteed to have a valid `EcoVec`. + ptr::drop_in_place(spilled); + } + } + } +} + +impl Default for EcoBytes { + #[inline] + fn default() -> Self { + Self::new() + } +} + +impl Debug for EcoBytes { + #[inline] + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + Debug::fmt(self.as_slice(), f) + } +} + +impl Hash for EcoBytes { + #[inline] + fn hash(&self, state: &mut H) { + self.as_slice().hash(state); + } +} + +impl Eq for EcoBytes {} + +impl PartialEq for EcoBytes { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.as_slice() == other.as_slice() + } +} + +impl PartialEq<[u8]> for EcoBytes { + #[inline] + fn eq(&self, other: &[u8]) -> bool { + self.as_slice() == other + } +} + +impl PartialEq<&[u8]> for EcoBytes { + #[inline] + fn eq(&self, other: &&[u8]) -> bool { + self.as_slice() == *other + } +} + +impl PartialEq<[u8; N]> for EcoBytes { + #[inline] + fn eq(&self, other: &[u8; N]) -> bool { + self.as_slice() == other + } +} + +impl PartialEq<&[u8; N]> for EcoBytes { + #[inline] + fn eq(&self, other: &&[u8; N]) -> bool { + self.as_slice() == *other + } +} + +impl PartialEq> for EcoBytes { + #[inline] + fn eq(&self, other: &Vec) -> bool { + self.as_slice() == other + } +} + +impl PartialEq for [u8] { + #[inline] + fn eq(&self, other: &EcoBytes) -> bool { + self == other.as_slice() + } +} + +impl PartialEq for [u8; N] { + #[inline] + fn eq(&self, other: &EcoBytes) -> bool { + self == other.as_slice() + } +} + +impl PartialEq for Vec { + #[inline] + fn eq(&self, other: &EcoBytes) -> bool { + self == other.as_slice() + } +} + +impl PartialEq> for EcoBytes { + #[inline] + fn eq(&self, other: &EcoVec) -> bool { + self.as_slice() == other.as_slice() + } +} + +impl PartialEq for EcoVec { + #[inline] + fn eq(&self, other: &EcoBytes) -> bool { + self.as_slice() == other.as_slice() + } +} + +impl Ord for EcoBytes { + #[inline] + fn cmp(&self, other: &Self) -> Ordering { + self.as_slice().cmp(other.as_slice()) + } +} + +impl PartialOrd for EcoBytes { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Deref for EcoBytes { + type Target = [u8]; + + #[inline] + fn deref(&self) -> &Self::Target { + self.as_slice() + } +} + +impl Borrow<[u8]> for EcoBytes { + #[inline] + fn borrow(&self) -> &[u8] { + self.as_slice() + } +} + +impl AsRef<[u8]> for EcoBytes { + #[inline] + fn as_ref(&self) -> &[u8] { + self.as_slice() + } +} + +impl From<&[u8]> for EcoBytes { + #[inline] + fn from(bytes: &[u8]) -> Self { + match InlineVec::from_slice(bytes) { + Ok(inline) => Self::from_inline(inline), + Err(()) => Self::from_eco(EcoVec::from(bytes)), + } + } +} + +impl From<&[u8; N]> for EcoBytes { + #[inline] + fn from(bytes: &[u8; N]) -> Self { + Self::from(bytes.as_slice()) + } +} + +impl From<[u8; N]> for EcoBytes { + #[inline] + fn from(bytes: [u8; N]) -> Self { + Self::from(bytes.as_slice()) + } +} + +impl From> for EcoBytes { + /// When the bytes do not fit inline, this needs to allocate to change the + /// layout. + #[inline] + fn from(bytes: Vec) -> Self { + if bytes.len() <= LIMIT { + Self::inline(&bytes) + } else { + Self::from_eco(EcoVec::from(bytes)) + } + } +} + +impl From<&Vec> for EcoBytes { + #[inline] + fn from(bytes: &Vec) -> Self { + Self::from(bytes.as_slice()) + } +} + +impl From> for EcoBytes { + /// This does not allocate. The resulting byte buffer remains spilled even + /// if its contents would fit inline. + #[inline] + fn from(bytes: EcoVec) -> Self { + Self::from_eco(bytes) + } +} + +impl From<&EcoBytes> for EcoBytes { + #[inline] + fn from(bytes: &EcoBytes) -> Self { + bytes.clone() + } +} + +impl From> for EcoBytes { + #[inline] + fn from(bytes: Cow<[u8]>) -> Self { + Self::from(&*bytes) + } +} + +impl From for Vec { + /// This needs to allocate to change the layout. + #[inline] + fn from(bytes: EcoBytes) -> Self { + bytes.as_slice().into() + } +} + +impl From for EcoVec { + /// When the byte buffer is stored inline, this needs to allocate to change + /// the layout. Otherwise, it reuses the existing allocation. + #[inline] + fn from(mut bytes: EcoBytes) -> Self { + match bytes.variant_mut() { + VariantMut::Inline(inline) => EcoVec::from(inline.as_slice()), + VariantMut::Spilled(spilled) => mem::take(spilled), + } + } +} + +impl From<&EcoBytes> for Vec { + #[inline] + fn from(bytes: &EcoBytes) -> Self { + bytes.as_slice().into() + } +} + +impl From<&EcoBytes> for EcoVec { + #[inline] + fn from(bytes: &EcoBytes) -> Self { + match bytes.variant() { + Variant::Inline(inline) => inline.as_slice().into(), + Variant::Spilled(spilled) => spilled.clone(), + } + } +} + +impl FromIterator for EcoBytes { + fn from_iter>(iter: I) -> Self { + let iter = iter.into_iter(); + let mut bytes = Self::with_capacity(iter.size_hint().0); + bytes.extend(iter); + bytes + } +} + +impl Extend for EcoBytes { + fn extend>(&mut self, iter: I) { + let iter = iter.into_iter(); + let hint = iter.size_hint().0; + if hint > 0 { + self.reserve(hint); + } + for byte in iter { + self.push(byte); + } + } +} + +impl<'a> Extend<&'a u8> for EcoBytes { + fn extend>(&mut self, iter: I) { + self.extend(iter.into_iter().copied()); + } +} + +impl<'a> IntoIterator for &'a EcoBytes { + type IntoIter = core::slice::Iter<'a, u8>; + type Item = &'a u8; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.as_slice().iter() + } +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub(crate) struct InlineVec { + /// Storage! + buf: [u8; LIMIT], + /// Invariant: After masking off the tag, never exceeds LIMIT. + tagged_len: u8, +} + +impl InlineVec { + #[inline] + pub const fn new() -> Self { + // Safety: Trivially, 0 <= LIMIT + unsafe { Self::from_buf([0; LIMIT], 0) } + } + + #[inline] + pub const fn from_slice(bytes: &[u8]) -> Result { + let len = bytes.len(); + if len > LIMIT { + return Err(()); + } + + let mut buf = [0; LIMIT]; + let mut i = 0; + while i < len { + buf[i] = bytes[i]; + i += 1; + } + + // Safety: If len > LIMIT, Err was returned earlier. + unsafe { Ok(Self::from_buf(buf, len)) } + } + + /// The given length may not exceed LIMIT. + #[inline] + pub const unsafe fn from_buf(buf: [u8; LIMIT], len: usize) -> Self { + debug_assert!(len <= LIMIT); + Self { buf, tagged_len: len as u8 | LEN_TAG } + } + + #[inline] + pub fn len(&self) -> usize { + usize::from(self.tagged_len & LEN_MASK) + } + + /// The given length may not exceed LIMIT. + #[inline] + unsafe fn set_len(&mut self, len: usize) { + debug_assert!(len <= LIMIT); + self.tagged_len = len as u8 | LEN_TAG; + } + + #[inline] + pub fn as_slice(&self) -> &[u8] { + // Safety: We have the invariant `len <= LIMIT`. + unsafe { self.buf.get_unchecked(..self.len()) } + } + + #[inline] + pub fn as_mut_slice(&mut self) -> &mut [u8] { + // Safety: We have the invariant `len <= LIMIT`. + let len = self.len(); + unsafe { self.buf.get_unchecked_mut(..len) } + } + + #[inline] + pub fn clear(&mut self) { + unsafe { + // Safety: Trivially, `0 <= LIMIT`. + self.set_len(0); + } + } + + #[inline] + pub fn push(&mut self, byte: u8) -> Result<(), ()> { + let len = self.len(); + if let Some(slot) = self.buf.get_mut(len) { + *slot = byte; + unsafe { + // Safety: The `get_mut` call guarantees that `len < LIMIT`. + self.set_len(len + 1); + } + Ok(()) + } else { + Err(()) + } + } + + #[inline] + pub fn pop(&mut self) -> Option { + let len = self.len(); + let byte = self.as_slice().last().copied()?; + unsafe { + // Safety: Finding a last element guarantees that `len > 0`, so the + // new length is in bounds. + self.set_len(len - 1); + } + Some(byte) + } + + #[inline] + pub fn insert(&mut self, index: usize, byte: u8) -> Result<(), ()> { + let len = self.len(); + if index > len { + out_of_bounds(index, len); + } + if len >= LIMIT { + return Err(()); + } + + let ptr = self.buf.as_mut_ptr(); + unsafe { + // Safety: + // - `index <= len < LIMIT`, as checked above. + // - The source is valid for `len - index` reads. + // - The destination is valid for `len - index` writes because the + // inline buffer has at least one free byte. + let at = ptr.add(index); + ptr::copy(at, at.add(1), len - index); + ptr::write(at, byte); + + // Safety: `len < LIMIT`, as checked above. + self.set_len(len + 1); + } + Ok(()) + } + + #[inline] + pub fn remove(&mut self, index: usize) -> u8 { + let len = self.len(); + if index >= len { + out_of_bounds(index, len); + } + + let ptr = self.buf.as_mut_ptr(); + unsafe { + // Safety: `index < len`, so this byte is initialized. + let at = ptr.add(index); + let byte = ptr::read(at); + + // Safety: + // - The source is valid for `len - index - 1` reads. + // - The destination is valid for the same number of writes. + ptr::copy(at.add(1), at, len - index - 1); + + // Safety: `index < len` guarantees `len > 0`. + self.set_len(len - 1); + byte + } + } + + #[inline] + pub fn extend_from_slice(&mut self, bytes: &[u8]) -> Result<(), ()> { + let len = self.len(); + let Some(grown) = len.checked_add(bytes.len()) else { return Err(()) }; + if let Some(segment) = self.buf.get_mut(len..grown) { + segment.copy_from_slice(bytes); + unsafe { + // Safety: The `get_mut` call guarantees that `grown <= LIMIT`. + self.set_len(grown); + } + Ok(()) + } else { + Err(()) + } + } + + #[inline] + pub fn insert_slice(&mut self, index: usize, bytes: &[u8]) -> Result<(), ()> { + let len = self.len(); + assert!(index <= len, "index {index} out of range for slice of length {len}"); + + let Some(grown) = len.checked_add(bytes.len()) else { return Err(()) }; + let tail_len = len - index; + if grown <= LIMIT { + let ptr = self.buf.as_mut_ptr(); + unsafe { + // Safety: Checked that `index <= len` and + // `len + bytes.len() == grown <= LIMIT`. + ptr::copy(ptr.add(index), ptr.add(index + bytes.len()), tail_len); + + // Safety: Checked that `index <= len` and + // `len + bytes.len() == grown <= LIMIT`. + ptr::copy_nonoverlapping(bytes.as_ptr(), ptr.add(index), bytes.len()); + + // Safety: Checked that `grown <= LIMIT`. + self.set_len(grown); + } + Ok(()) + } else { + Err(()) + } + } + + #[inline] + pub fn truncate(&mut self, target: usize) { + if target < self.len() { + unsafe { + // Safety: Checked that it's smaller than the current length, + // which cannot exceed LIMIT itself. + self.set_len(target); + } + } + } + + #[inline] + pub fn remove_range(&mut self, range: R) + where + R: RangeBounds, + { + let len = self.len(); + let range = crate::vendor::slice::range(range, ..len); + + let tail_len = len - range.end; + let target = len - range.len(); + let ptr = self.buf.as_mut_ptr(); + unsafe { + // Safety: The range is in bounds + ptr::copy(ptr.add(range.end), ptr.add(range.start), tail_len); + + // Safety: Checked that it's smaller than the current length, + // which cannot exceed LIMIT itself. + self.set_len(target); + } + } +} + +#[cold] +const fn exceeded_inline_capacity() -> ! { + panic!("exceeded inline capacity"); +} + +#[cold] +fn out_of_bounds(index: usize, len: usize) -> ! { + panic!("index is out bounds (index: {index}, len: {len})"); +} + +#[cfg(feature = "std")] +impl std::io::Write for EcoBytes { + #[inline] + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.extend_from_slice(buf); + Ok(buf.len()) + } + + #[inline] + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} + +#[cfg(feature = "serde")] +mod serde { + use super::EcoBytes; + use core::fmt; + use serde::de::{Deserializer, SeqAccess, Visitor}; + + impl serde::Serialize for EcoBytes { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_bytes(self.as_slice()) + } + } + + struct EcoBytesVisitor; + + impl<'de> Visitor<'de> for EcoBytesVisitor { + type Value = EcoBytes; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a byte buffer") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let mut bytes = EcoBytes::with_capacity(seq.size_hint().unwrap_or(0)); + while let Some(byte) = seq.next_element()? { + bytes.push(byte); + } + Ok(bytes) + } + + fn visit_bytes(self, bytes: &[u8]) -> Result + where + E: serde::de::Error, + { + Ok(EcoBytes::from(bytes)) + } + + fn visit_str(self, string: &str) -> Result + where + E: serde::de::Error, + { + self.visit_bytes(string.as_bytes()) + } + } + + impl<'de> serde::Deserialize<'de> for EcoBytes { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_bytes(EcoBytesVisitor) + } + } +} diff --git a/src/dynamic.rs b/src/dynamic.rs deleted file mode 100644 index a6a0303..0000000 --- a/src/dynamic.rs +++ /dev/null @@ -1,444 +0,0 @@ -use core::mem::{self, ManuallyDrop}; -use core::ops::RangeBounds; -use core::ptr; - -use super::EcoVec; - -/// A byte vector that can hold up to 15 bytes inline and then spills to an -/// `EcoVec`. -pub(crate) struct DynamicVec(Repr); - -/// The internal representation. -/// -/// On 64-bit little endian, this assumes that no valid EcoVec exists in which -/// the highest-order bit of the InlineVec's `tagged_len` would be set. This is -/// true because EcoVec is repr(C) and its second field `len` is bounded by -/// `isize::MAX`. On 32-bit, it's no problem and for 64-bit big endian, we -/// have an increased limit to prevent the overlap. -#[repr(C)] -union Repr { - inline: InlineVec, - spilled: ManuallyDrop>, -} - -/// This is never stored in memory, it's just an abstraction for safe access. -#[derive(Debug)] -enum Variant<'a> { - Inline(&'a InlineVec), - Spilled(&'a EcoVec), -} - -/// This is never stored in memory, it's just an abstraction for safe access. -#[derive(Debug)] -enum VariantMut<'a> { - Inline(&'a mut InlineVec), - Spilled(&'a mut EcoVec), -} - -/// The maximum amount of inline storage. Typically, this is 15 bytes. -/// -/// However, in the rare exotic system, we still want things to be safe. -/// Therefore, the following special cases: -/// - For big endian, we increase the limit such that the tagged length of the -/// inline variants doesn't overlap with the EcoVec. For little endian, it's -/// fine since the highest order bit is never set for a valid EcoVec. -/// - In case somehow EcoVec is very big (128-bit pointers woah), increase the -/// limit too. -pub(crate) const LIMIT: usize = { - let mut limit = 15; - if limit < mem::size_of::>() - 1 { - limit = mem::size_of::>() - 1; - } - if cfg!(target_endian = "big") { - limit += mem::size_of::(); - } - limit -}; - -/// This bit is used to check whether we are inline or not. On 64-bit little -/// endian, it coincides with the highest-order bit of an EcoVec's length, which -/// can't be set because the EcoVec's length never exceeds `isize::MAX`. -const LEN_TAG: u8 = 0b1000_0000; - -/// This is used to mask off the tag to get the inline variant's length. -const LEN_MASK: u8 = 0b0111_1111; - -impl DynamicVec { - #[inline] - pub const fn new() -> Self { - Self::from_inline(InlineVec::new()) - } - - #[inline] - pub const fn from_inline(inline: InlineVec) -> Self { - Self(Repr { inline }) - } - - #[inline] - pub const fn from_eco(vec: EcoVec) -> Self { - // Safety: - // Explicitly set `tagged_len` to 0 to mark this as a spilled variant. - // Just initializing with `Repr { spilled: ... }` would leave - // `tagged_len` uninitialized, leading to undefined behaviour on access. - let mut repr = Repr { - inline: InlineVec { buf: [0; LIMIT], tagged_len: 0 }, - }; - repr.spilled = ManuallyDrop::new(vec); - Self(repr) - } - - #[inline] - pub fn from_slice(bytes: &[u8]) -> Self { - match InlineVec::from_slice(bytes) { - Ok(inline) => Self::from_inline(inline), - _ => Self::from_eco(EcoVec::from(bytes)), - } - } - - #[inline] - pub fn with_capacity(capacity: usize) -> Self { - if capacity <= LIMIT { - Self::new() - } else { - Self::from_eco(EcoVec::with_capacity(capacity)) - } - } - - #[inline] - pub fn len(&self) -> usize { - match self.variant() { - Variant::Inline(inline) => inline.len(), - Variant::Spilled(spilled) => spilled.len(), - } - } - - #[inline] - pub fn as_slice(&self) -> &[u8] { - match self.variant() { - Variant::Inline(inline) => inline.as_slice(), - Variant::Spilled(spilled) => spilled.as_slice(), - } - } - - #[inline] - pub fn make_mut(&mut self) -> &mut [u8] { - match self.variant_mut() { - VariantMut::Inline(inline) => inline.as_mut_slice(), - VariantMut::Spilled(spilled) => spilled.make_mut(), - } - } - - #[inline] - pub fn push(&mut self, byte: u8) { - match self.variant_mut() { - VariantMut::Inline(inline) => { - if inline.push(byte).is_err() { - let mut eco = EcoVec::with_capacity(LIMIT * 2); - eco.extend_from_byte_slice(self.as_slice()); - eco.push(byte); - *self = Self::from_eco(eco); - } - } - VariantMut::Spilled(spilled) => { - spilled.push(byte); - } - } - } - - #[inline] - pub fn extend_from_slice(&mut self, bytes: &[u8]) { - match self.variant_mut() { - VariantMut::Inline(inline) => { - if inline.extend_from_slice(bytes).is_err() { - let needed = inline.len() + bytes.len(); - let mut eco = EcoVec::with_capacity(needed.next_power_of_two()); - eco.extend_from_byte_slice(self.as_slice()); - eco.extend_from_byte_slice(bytes); - *self = Self::from_eco(eco); - } - } - VariantMut::Spilled(spilled) => { - spilled.extend_from_byte_slice(bytes); - } - } - } - - #[inline] - pub fn insert_slice(&mut self, index: usize, bytes: &[u8]) { - match self.variant_mut() { - VariantMut::Inline(inline) => { - if inline.insert_slice(index, bytes).is_err() { - let needed = inline.len() + bytes.len(); - let mut eco = EcoVec::with_capacity(needed.next_power_of_two()); - let (a, b) = inline.as_slice().split_at(index); - eco.extend_from_byte_slice(a); - eco.extend_from_byte_slice(bytes); - eco.extend_from_byte_slice(b); - *self = Self::from_eco(eco); - } - } - VariantMut::Spilled(spilled) => { - spilled.splice(index..index, bytes.iter().copied()); - } - } - } - - #[inline] - pub fn clear(&mut self) { - match self.variant_mut() { - VariantMut::Inline(inline) => inline.clear(), - VariantMut::Spilled(spilled) => spilled.clear(), - } - } - - #[inline] - pub fn truncate(&mut self, target: usize) { - match self.variant_mut() { - VariantMut::Inline(inline) => inline.truncate(target), - VariantMut::Spilled(spilled) => spilled.truncate(target), - } - } - - #[inline] - pub fn remove_range(&mut self, range: R) - where - R: RangeBounds, - { - match self.variant_mut() { - VariantMut::Inline(inline) => inline.remove_range(range), - VariantMut::Spilled(spilled) => { - spilled.drain(range); - } - } - } -} - -impl DynamicVec { - // If this returns true, guarantees that `self.0.inline` is initialized. - // Otherwise, guarantees that `self.0.spilled` is initialized. - #[inline] - fn is_inline(&self) -> bool { - // Safety: - // We always initialize tagged_len, even for the `EcoVec` variant. For - // the inline variant the highest-order bit is always `1`. For the - // spilled variant, it is initialized with `0` and cannot deviate from - // that because the EcoVec's `len` field is bounded by `isize::MAX`. (At - // least on 64-bit little endian; on 32-bit or big-endian the EcoVec - // and tagged_len fields don't even overlap, meaning tagged_len stays at - // its initial value.) - unsafe { self.0.inline.tagged_len & LEN_TAG != 0 } - } - - #[inline] - fn variant(&self) -> Variant<'_> { - unsafe { - // Safety: - // We access the respective variant only if the check passes. - if self.is_inline() { - Variant::Inline(&self.0.inline) - } else { - Variant::Spilled(&self.0.spilled) - } - } - } - - #[inline] - fn variant_mut(&mut self) -> VariantMut<'_> { - unsafe { - // Safety: - // We access the respective variant only if the check passes. - if self.is_inline() { - VariantMut::Inline(&mut self.0.inline) - } else { - VariantMut::Spilled(&mut self.0.spilled) - } - } - } -} - -impl Clone for DynamicVec { - #[inline] - fn clone(&self) -> Self { - match self.variant() { - Variant::Inline(inline) => Self::from_inline(*inline), - Variant::Spilled(spilled) => Self::from_eco(spilled.clone()), - } - } -} - -impl Drop for DynamicVec { - #[inline] - fn drop(&mut self) { - if let VariantMut::Spilled(spilled) = self.variant_mut() { - unsafe { - // Safety: We are guaranteed to have a valid `EcoVec`. - ptr::drop_in_place(spilled); - } - } - } -} - -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub(crate) struct InlineVec { - /// Storage! - buf: [u8; LIMIT], - /// Invariant: After masking off the tag, never exceeds LIMIT. - tagged_len: u8, -} - -impl InlineVec { - #[inline] - pub const fn new() -> Self { - // Safety: Trivially, 0 <= LIMIT - unsafe { Self::from_buf([0; LIMIT], 0) } - } - - #[inline] - pub const fn from_slice(bytes: &[u8]) -> Result { - let len = bytes.len(); - if len > LIMIT { - return Err(()); - } - - let mut buf = [0; LIMIT]; - let mut i = 0; - while i < len { - buf[i] = bytes[i]; - i += 1; - } - - // Safety: If len > LIMIT, Err was returned earlier. - unsafe { Ok(Self::from_buf(buf, len)) } - } - - /// The given length may not exceed LIMIT. - #[inline] - pub const unsafe fn from_buf(buf: [u8; LIMIT], len: usize) -> Self { - debug_assert!(len <= LIMIT); - Self { buf, tagged_len: len as u8 | LEN_TAG } - } - - #[inline] - pub fn len(&self) -> usize { - usize::from(self.tagged_len & LEN_MASK) - } - - /// The given length may not exceed LIMIT. - #[inline] - unsafe fn set_len(&mut self, len: usize) { - debug_assert!(len <= LIMIT); - self.tagged_len = len as u8 | LEN_TAG; - } - - #[inline] - pub fn as_slice(&self) -> &[u8] { - // Safety: We have the invariant `len <= LIMIT`. - unsafe { self.buf.get_unchecked(..self.len()) } - } - - #[inline] - pub fn as_mut_slice(&mut self) -> &mut [u8] { - // Safety: We have the invariant `len <= LIMIT`. - let len = self.len(); - unsafe { self.buf.get_unchecked_mut(..len) } - } - - #[inline] - pub fn clear(&mut self) { - unsafe { - // Safety: Trivially, `0 <= LIMIT`. - self.set_len(0); - } - } - - #[inline] - pub fn push(&mut self, byte: u8) -> Result<(), ()> { - let len = self.len(); - if let Some(slot) = self.buf.get_mut(len) { - *slot = byte; - unsafe { - // Safety: The `get_mut` call guarantees that `len < LIMIT`. - self.set_len(len + 1); - } - Ok(()) - } else { - Err(()) - } - } - - #[inline] - pub fn extend_from_slice(&mut self, bytes: &[u8]) -> Result<(), ()> { - let len = self.len(); - let grown = len + bytes.len(); - if let Some(segment) = self.buf.get_mut(len..grown) { - segment.copy_from_slice(bytes); - unsafe { - // Safety: The `get_mut` call guarantees that `grown <= LIMIT`. - self.set_len(grown); - } - Ok(()) - } else { - Err(()) - } - } - - #[inline] - pub fn insert_slice(&mut self, index: usize, bytes: &[u8]) -> Result<(), ()> { - let len = self.len(); - assert!(index <= len, "index {index} out of range for slice of length {len}"); - - let grown = len + bytes.len(); - let tail_len = len - index; - if grown <= LIMIT { - let ptr = self.buf.as_mut_ptr(); - unsafe { - // Safety: Checked that `index <= len` and - // `len + bytes.len() == grown < LIMIT`. - ptr::copy(ptr.add(index), ptr.add(index + bytes.len()), tail_len); - - // Safety: Checked that `index <= len` and - // `len + bytes.len() == grown < LIMIT`. - ptr::copy_nonoverlapping(bytes.as_ptr(), ptr.add(index), bytes.len()); - - // Safety: Checked that `grown < LIMIT` - self.set_len(grown); - } - Ok(()) - } else { - Err(()) - } - } - - #[inline] - pub fn truncate(&mut self, target: usize) { - if target < self.len() { - unsafe { - // Safety: Checked that it's smaller than the current length, - // which cannot exceed LIMIT itself. - self.set_len(target); - } - } - } - - #[inline] - pub fn remove_range(&mut self, range: R) - where - R: RangeBounds, - { - let len = self.len(); - let range = crate::vendor::slice::range(range, ..len); - - let tail_len = len - range.end; - let target = len - range.len(); - let ptr = self.buf.as_mut_ptr(); - unsafe { - // Safety: The range is in bounds - ptr::copy(ptr.add(range.end), ptr.add(range.start), tail_len); - - // Safety: Checked that it's smaller than the current length, - // which cannot exceed LIMIT itself. - self.set_len(target); - } - } -} diff --git a/src/lib.rs b/src/lib.rs index e108956..f15b292 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,16 +1,20 @@ /*! -Compact, clone-on-write vector and string. +Compact, clone-on-write vector, string, and byte buffer. ## Types -- An [`EcoVec`] is a reference-counted clone-on-write vector. It takes up two +- [`EcoVec`] is a reference-counted clone-on-write vector. It takes up two words of space (= 2 usize) and has the same memory layout as a `&[T]` slice. Within its allocation, it stores a reference count, its capacity and its elements. -- An [`EcoString`] is a reference-counted clone-on-write string with inline +- [`EcoString`] is a reference-counted clone-on-write string with inline storage. It takes up 16 bytes of space. It has 15 bytes of inline storage and starting from 16 bytes it becomes an [`EcoVec`]. +- [`EcoBytes`] is a reference-counted clone-on-write byte buffer with inline + storage. Like an [`EcoString`], it takes up 16 bytes and stores up to 15 bytes + inline before spilling to an [`EcoVec`]. + ## Example ``` // This is stored inline. @@ -45,12 +49,13 @@ assert_eq!(third, "Welcome to earth! "); extern crate alloc; +pub mod bytes; pub mod string; pub mod vec; -mod dynamic; mod vendor; +pub use self::bytes::EcoBytes; pub use self::string::EcoString; pub use self::vec::EcoVec; diff --git a/src/string.rs b/src/string.rs index bbf7f3e..a371199 100644 --- a/src/string.rs +++ b/src/string.rs @@ -15,7 +15,8 @@ use std::path::Path; #[cfg(not(feature = "std"))] use alloc::string::String; -use crate::dynamic::{DynamicVec, InlineVec}; +use crate::bytes::{EcoBytes, InlineVec}; +use crate::EcoVec; /// Create a new [`EcoString`] from a format string. /// ``` @@ -66,7 +67,7 @@ macro_rules! eco_format { /// 64-bit big-endian systems, the type's size increases to 24 bytes and the /// amount of inline storage to 23 bytes. #[derive(Clone)] -pub struct EcoString(DynamicVec); +pub struct EcoString(EcoBytes); impl EcoString { /// Maximum number of bytes for an inline `EcoString` before spilling on @@ -76,12 +77,12 @@ impl EcoString { /// /// # Note /// This value is semver exempt and can be changed with any update. - pub const INLINE_LIMIT: usize = crate::dynamic::LIMIT; + pub const INLINE_LIMIT: usize = EcoBytes::INLINE_LIMIT; /// Create a new, empty string. #[inline] pub const fn new() -> Self { - Self(DynamicVec::new()) + Self(EcoBytes::new()) } /// Create a new, inline string. @@ -90,22 +91,31 @@ impl EcoString { /// storage. #[inline] pub const fn inline(string: &str) -> Self { - let Ok(inline) = InlineVec::from_slice(string.as_bytes()) else { - exceeded_inline_capacity(); - }; - Self(DynamicVec::from_inline(inline)) + Self(EcoBytes::inline(string.as_bytes())) + } + + /// Try to create a new, inline string. + /// + /// Returns `None` if the string's length exceeds the capacity of the inline + /// storage. + #[inline] + pub const fn try_inline(string: &str) -> Option { + match InlineVec::from_slice(string.as_bytes()) { + Ok(inline) => Some(Self(EcoBytes::from_inline(inline))), + Err(()) => None, + } } /// Create a new, empty string with the given `capacity`. #[inline] pub fn with_capacity(capacity: usize) -> Self { - Self(DynamicVec::with_capacity(capacity)) + Self(EcoBytes::with_capacity(capacity)) } /// Create an instance from a string slice. #[inline] fn from_str(string: &str) -> Self { - Self(DynamicVec::from_slice(string.as_bytes())) + Self(EcoBytes::from(string.as_bytes())) } /// Whether the string is empty. @@ -114,6 +124,12 @@ impl EcoString { self.len() == 0 } + /// Whether this string is stored inline. + #[inline] + pub fn is_inline(&self) -> bool { + self.0.is_inline() + } + /// The length of the string in bytes. #[inline] pub fn len(&self) -> usize { @@ -282,13 +298,7 @@ impl EcoString { /// Repeat this string `n` times. pub fn repeat(&self, n: usize) -> Self { - let slice = self.as_bytes(); - let capacity = slice.len().saturating_mul(n); - let mut vec = DynamicVec::with_capacity(capacity); - for _ in 0..n { - vec.extend_from_slice(slice); - } - Self(vec) + Self(self.0.repeat(n)) } } @@ -585,6 +595,44 @@ impl From<&EcoString> for String { } } +impl From for EcoBytes { + /// This does not allocate. + #[inline] + fn from(string: EcoString) -> Self { + string.0 + } +} + +impl TryFrom for EcoString { + type Error = core::str::Utf8Error; + + /// This validates UTF-8 without allocating. + #[inline] + fn try_from(bytes: EcoBytes) -> Result { + core::str::from_utf8(&bytes)?; + Ok(Self(bytes)) + } +} + +impl From for EcoVec { + /// When the string is stored inline, this needs to allocate to change the + /// layout. Otherwise, it reuses the existing allocation. + #[inline] + fn from(string: EcoString) -> Self { + string.0.into() + } +} + +impl TryFrom> for EcoString { + type Error = core::str::Utf8Error; + + /// This validates UTF-8 without allocating. + #[inline] + fn try_from(bytes: EcoVec) -> Result { + Self::try_from(EcoBytes::from(bytes)) + } +} + impl FromStr for EcoString { type Err = core::convert::Infallible; @@ -609,11 +657,6 @@ impl ToEcoString for T { } } -#[cold] -const fn exceeded_inline_capacity() -> ! { - panic!("exceeded inline capacity"); -} - #[cfg(feature = "serde")] mod serde { use crate::EcoString; diff --git a/tests/tests.rs b/tests/tests.rs index 8ec6d01..1c195ee 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -10,7 +10,7 @@ use std::mem; use std::sync::atomic::{AtomicUsize, Ordering::*}; use ecow::string::ToEcoString; -use ecow::{eco_format, eco_vec, EcoString, EcoVec}; +use ecow::{eco_format, eco_vec, EcoBytes, EcoString, EcoVec}; const ALPH: &str = "abcdefghijklmnopqrstuvwxyz"; const LIMIT: usize = EcoString::INLINE_LIMIT; @@ -24,6 +24,8 @@ fn test_mem_size() { let word = mem::size_of::(); assert_eq!(mem::size_of::>(), 2 * word); assert_eq!(mem::size_of::>>(), 2 * word); + assert_eq!(mem::size_of::(), mem::size_of::()); + assert_eq!(mem::size_of::>(), mem::size_of::>()); if cfg!(target_endian = "little") { if cfg!(target_pointer_width = "32") { @@ -371,6 +373,105 @@ fn test_array_from_vec() { assert_eq!(<[String; 0]>::try_from(EcoVec::new()).unwrap(), <[String; 0]>::default()); } +#[test] +fn test_bytes_new() { + assert_eq!(EcoBytes::new(), &[]); + assert_eq!(EcoBytes::from([1, 2, 3]), [1, 2, 3]); + + let inline = EcoBytes::from(&ALPH.as_bytes()[..LIMIT]); + let spilled = EcoBytes::from(&ALPH.as_bytes()[..LIMIT + 1]); + assert!(inline.is_inline()); + assert!(!spilled.is_inline()); +} + +#[test] +fn test_bytes_inline_okay() { + const BYTES: EcoBytes = EcoBytes::inline(b"hello"); + assert_eq!(BYTES, b"hello"); + assert_eq!(EcoBytes::try_inline(b"hello").unwrap(), b"hello"); + assert!(EcoBytes::try_inline(ALPH.as_bytes()).is_none()); +} + +#[test] +#[should_panic(expected = "exceeded inline capacity")] +fn test_bytes_inline_capacity_exceeded() { + EcoBytes::inline(ALPH.as_bytes()); +} + +#[test] +fn test_bytes_push() { + let mut bytes = EcoBytes::from(&ALPH.as_bytes()[..LIMIT]); + let original = bytes.clone(); + + bytes.push(b'!'); + assert_eq!(bytes.pop(), Some(b'!')); + assert_eq!(bytes, original); + assert!(!bytes.is_inline()); +} + +#[test] +fn test_bytes_insert() { + let mut bytes = EcoBytes::from([1, 2, 3]); + bytes.insert(1, 4); + assert_eq!(bytes, [1, 4, 2, 3]); + + let mut bytes = EcoBytes::from(&ALPH.as_bytes()[..LIMIT]); + bytes.insert(LIMIT / 2, b'_'); + assert_eq!(bytes[LIMIT / 2], b'_'); + assert!(!bytes.is_inline()); +} + +#[test] +#[should_panic(expected = "index is out bounds (index: 4, len: 3)")] +fn test_bytes_insert_fail() { + EcoBytes::from([1, 2, 3]).insert(4, 0); +} + +#[test] +fn test_bytes_remove() { + let mut bytes = EcoBytes::from([1, 2, 3]); + assert_eq!(bytes.remove(1), 2); + assert_eq!(bytes, [1, 3]); + + let mut bytes = EcoBytes::from(ALPH.as_bytes()); + let clone = bytes.clone(); + assert_eq!(bytes.remove(1), b'b'); + assert_eq!(clone, ALPH.as_bytes()); +} + +#[test] +#[should_panic(expected = "index is out bounds (index: 4, len: 3)")] +fn test_bytes_remove_fail() { + EcoBytes::from([1, 2, 3]).remove(4); +} + +#[test] +fn test_bytes_make_mut() { + let mut first = EcoBytes::from(ALPH.as_bytes()); + let second = first.clone(); + first.make_mut()[0] = b'A'; + assert_eq!(first[0], b'A'); + assert_eq!(second[0], b'a'); +} + +#[test] +fn test_bytes_conversions() { + let string = EcoString::from(ALPH); + let ptr = string.as_ptr(); + let bytes = EcoBytes::from(string); + assert_eq!(bytes.as_ptr(), ptr); + + let string = EcoString::try_from(bytes).unwrap(); + assert_eq!(string.as_ptr(), ptr); + assert_eq!(string, ALPH); + assert!(EcoString::try_from(EcoBytes::from([0xFF])).is_err()); + + let vec = EcoVec::from(*b"abcdefghijklmnopqrstuvwxyz"); + let ptr = vec.as_ptr(); + let bytes = EcoBytes::from(vec); + assert_eq!(EcoVec::from(bytes).as_ptr(), ptr); +} + #[test] fn test_str_macro() { assert_eq!( @@ -541,6 +642,10 @@ fn test_str_repeat() { #[test] fn test_str_inline_okay() { assert_eq!(EcoString::inline("hello"), "hello"); + assert_eq!(EcoString::try_inline("hello").unwrap(), "hello"); + assert!(EcoString::try_inline(ALPH).is_none()); + assert!(EcoString::from("hello").is_inline()); + assert!(!EcoString::from(ALPH).is_inline()); } #[test]