diff --git a/src/bytedata.rs b/src/bytedata.rs index 8df24d2..278ade9 100644 --- a/src/bytedata.rs +++ b/src/bytedata.rs @@ -913,6 +913,27 @@ impl From<Vec<u8>> for ByteData<'_> { } } +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +impl<'a> From<alloc::borrow::Cow<'a, [u8]>> for ByteData<'a> { + #[inline] + fn from(data: alloc::borrow::Cow<'a, [u8]>) -> Self { + ByteData::from_cow(data) + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +impl<'a> From<alloc::borrow::Cow<'a, str>> for ByteData<'a> { + #[inline] + fn from(data: alloc::borrow::Cow<'a, str>) -> Self { + match data { + alloc::borrow::Cow::Borrowed(borr) => Self::from_borrowed(borr.as_bytes()), + alloc::borrow::Cow::Owned(ow) => Self::from(ow), + } + } +} + #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] impl<'a> From<ByteData<'a>> for Vec<u8> { diff --git a/src/macros.rs b/src/macros.rs index a26ad40..36bd697 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -45,6 +45,8 @@ macro_rules! concat_str_static { #[cfg(feature = "alloc")] #[doc(hidden)] #[inline] +#[allow(clippy::unwrap_used)] +#[must_use] pub fn __format_shared<'a>(args: core::fmt::Arguments<'_>) -> crate::StringData<'a> { if let Some(args2) = args.as_str() { return crate::StringData::from_static(args2); @@ -68,10 +70,11 @@ macro_rules! format_shared { }; } -#[cfg(feature = "queue")] +#[cfg(all(feature = "queue", feature = "alloc"))] #[doc(hidden)] #[inline] #[allow(clippy::unwrap_used)] +#[must_use] pub fn __format_queue<'a>(args: core::fmt::Arguments<'_>) -> crate::StringQueue<'a> { if let Some(args2) = args.as_str() { return crate::StringQueue::with_item(crate::StringData::from_static(args2)); @@ -84,8 +87,9 @@ pub fn __format_queue<'a>(args: core::fmt::Arguments<'_>) -> crate::StringQueue< /// Formats a format string with arguments into an owned `StringQueue`. /// /// There is currently no way to optimize shallow clones of `StringData` or `StringQueue` instances, so prefer to use [`StringQueue::push_back`] or [`StringQueue::append`] to build a queue of prepared strings. -#[cfg(feature = "queue")] +#[cfg(all(feature = "queue", feature = "alloc"))] #[cfg_attr(docsrs, doc(cfg(feature = "macros")))] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] #[cfg_attr(docsrs, doc(cfg(feature = "queue")))] #[macro_export] macro_rules! format_queue { diff --git a/src/queue/byte_queue.rs b/src/queue/byte_queue.rs index 2adc02d..7073210 100644 --- a/src/queue/byte_queue.rs +++ b/src/queue/byte_queue.rs @@ -422,13 +422,23 @@ impl<'a> ByteQueue<'a> { OwnedByteIter::new(self) } - /// Adds another `ByteQueue`'s chunks to this queue. May be optimized in the future. + /// Adds another `ByteQueue`'s chunks to this queue. #[inline] + #[cfg(not(feature = "alloc"))] pub fn append(&mut self, other: Self) { - // TODO: optimize by adding full regions instead of just chunks at to save on region allocations self.extend(other.into_iter()); } + /// Adds another `ByteQueue`'s chunks to this queue. + #[inline] + #[cfg(feature = "alloc")] + pub fn append(&mut self, mut other: Self) { + self.remain += other.remain; + other.remain = 0; + self.queue + .append(core::mem::replace(&mut other.queue, LinkedRoot::new())); + } + /// Split the queue at a certain index. /// This will return the part of the queue after the index `[at, len)` and keep everything before the position in the original queue `[0, at)`. /// @@ -851,7 +861,7 @@ impl core::cmp::Ord for ByteQueue<'_> { } #[allow(clippy::wildcard_enum_match_arm)] match av.cmp(bv) { - core::cmp::Ordering::Equal => continue, + core::cmp::Ordering::Equal => (), x => return x, } } @@ -895,7 +905,7 @@ impl PartialOrd<[u8]> for ByteQueue<'_> { } #[allow(clippy::wildcard_enum_match_arm)] match av.cmp(bv) { - core::cmp::Ordering::Equal => continue, + core::cmp::Ordering::Equal => (), xy => return Some(xy), } } @@ -996,3 +1006,13 @@ impl Default for ByteQueue<'_> { ByteQueue::new() } } + +#[cfg(feature = "alloc")] +impl core::fmt::Write for crate::ByteQueue<'_> { + #[inline] + #[allow(clippy::min_ident_chars)] + fn write_str(&mut self, s: &str) -> core::fmt::Result { + self.push_back(crate::ByteData::from_borrowed(s.as_bytes()).into_shared()); + Ok(()) + } +} diff --git a/src/queue/linked_root.rs b/src/queue/linked_root.rs index 52cb974..91962b3 100644 --- a/src/queue/linked_root.rs +++ b/src/queue/linked_root.rs @@ -3,9 +3,11 @@ use crate::ByteData; #[cfg(feature = "alloc")] pub(super) struct LinkedRoot<'a> { + /// The first chunk in the queue, available for optimized use. pub(super) chamber: ByteData<'a>, pub(super) first: *mut super::linked_node_leaf::LinkedNodeLeaf<'a>, pub(super) last: *mut super::linked_node_leaf::LinkedNodeLeaf<'a>, + /// The number of chunks in the queue. pub(super) count: usize, } @@ -246,6 +248,60 @@ impl<'a> LinkedRoot<'a> { // SAFETY: if the pointer is non-null it points to a valid `LinkedNodeLeaf`. LinkedIter::new(chamber, unsafe { self.first.as_ref() }) } + + pub(super) fn append(&mut self, mut other: Self) { + if other.count == 0 { + return; + } + if self.count == 0 { + *self = other; + return; + } + if self.count == 1 && !self.chamber.is_empty() { + other.push_front(core::mem::replace( + &mut self.chamber, + ByteData::from_chunk(&[0]), + )); + *self = other; + return; + } + + // attempt to move the chambered item without allocating + if !other.chamber.is_empty() { + let chamber = core::mem::replace(&mut other.chamber, ByteData::empty()); + other.count -= 1; + // SAFETY: if the pointer is non-null it points to a valid `LinkedNodeLeaf`. + if let Some(fst) = unsafe { other.first.as_mut() } { + if let Err(val) = fst.data.push_front(chamber) { + self.push_back(val); + } + } else { + self.push_back(chamber); + } + if other.count == 0 { + return; + } + } + + // SAFETY: if the pointer is non-null it points to a valid `LinkedNodeLeaf`. + if let Some(last) = unsafe { self.last.as_mut() } { + // SAFETY: if the pointer is non-null it points to a valid `LinkedNodeLeaf`. + if let Some(first) = unsafe { other.first.as_mut() } { + last.next = first; + first.prev = last; + self.last = other.last; + } else { + unreachable!("invalid state at append"); + } + } else { + self.first = other.first; + self.last = other.last; + } + self.count += other.count; + other.first = core::ptr::null_mut(); + other.last = core::ptr::null_mut(); + other.count = 0; + } } impl<'a> LinkedRoot<'a> { @@ -285,12 +341,6 @@ impl<'a> LinkedRoot<'a> { return; } #[cfg(feature = "alloc")] - if self.count == 0 { - self.chamber = data; - self.count = 1; - return; - } - #[cfg(feature = "alloc")] { core::mem::swap(&mut self.chamber, &mut data); if data.is_empty() { diff --git a/src/queue/string_queue.rs b/src/queue/string_queue.rs index 45618fa..848de24 100644 --- a/src/queue/string_queue.rs +++ b/src/queue/string_queue.rs @@ -537,6 +537,8 @@ impl core::fmt::Debug for crate::StringQueue<'_> { } } +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] impl core::fmt::Write for crate::StringQueue<'_> { #[inline] #[allow(clippy::min_ident_chars)] diff --git a/src/stringdata.rs b/src/stringdata.rs index 3d4bac5..43f1d3f 100644 --- a/src/stringdata.rs +++ b/src/stringdata.rs @@ -471,6 +471,18 @@ impl From<String> for StringData<'_> { } } +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +impl<'a> From<alloc::borrow::Cow<'a, str>> for StringData<'a> { + #[inline] + fn from(data: alloc::borrow::Cow<'a, str>) -> Self { + match data { + alloc::borrow::Cow::Borrowed(borr) => Self::from_borrowed(borr), + alloc::borrow::Cow::Owned(ow) => Self::from_owned(ow), + } + } +} + #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] impl<'a> From<StringData<'a>> for String { diff --git a/src/test/macros.rs b/src/test/macros.rs index 8e16b93..8675d11 100644 --- a/src/test/macros.rs +++ b/src/test/macros.rs @@ -23,7 +23,7 @@ fn test_macros_format_shared() { assert!(hw.ends_with(choice)); } -#[cfg(feature = "queue")] +#[cfg(all(feature = "alloc", feature = "queue"))] #[test] fn test_macros_format_queue() { static CHOICES: &[&str] = &[" you", " world", "... hello... hello", "oooooooo!"]; diff --git a/src/test/queue.rs b/src/test/queue.rs index 2546984..fc253c8 100644 --- a/src/test/queue.rs +++ b/src/test/queue.rs @@ -57,3 +57,45 @@ fn byte_queue_test() { panic!("queue is not the same as the input data (from end)\r\n queue: {queue:?}\r\n ref_data: {ref_data:?}"); } } + +#[test] +#[allow(clippy::panic)] +fn byte_queue_append_test() { + static A_DATA: &[&[u8]] = &[ + b"a0", b"a1", b"a2", b"a3", b"a4", b"a5", b"a6", b"a7", b"a8", b"a9", b"aA", b"aB", b"aC", + b"aD", b"aE", b"aF", + ]; + static B_DATA: &[&[u8]] = &[ + b"b0", b"b1", b"b2", b"b3", b"b4", b"b5", b"b6", b"b7", b"b8", b"b9", b"bA", b"bB", b"bC", + b"bD", b"bE", b"bF", b"bG", b"bH", b"bI", + ]; + + let mut a_queue = crate::ByteQueue::new(); + for data in A_DATA { + a_queue.push_back(*data); + } + for (chunk, data) in a_queue.chunks().zip(A_DATA) { + assert_eq!(chunk.as_slice(), *data); + } + + let mut b_queue = crate::ByteQueue::new(); + for data in B_DATA { + b_queue.push_back(*data); + } + for (chunk, data) in b_queue.chunks().zip(B_DATA) { + assert_eq!(chunk.as_slice(), *data); + } + + a_queue.append(b_queue); + + let mut i = 0; + while let Some(chunk) = a_queue.pop_front() { + if i < A_DATA.len() { + assert_eq!(chunk.as_slice(), A_DATA[i]); + } else { + assert_eq!(chunk.as_slice(), B_DATA[i - A_DATA.len()]); + } + i += 1; + } + assert_eq!(i, A_DATA.len() + B_DATA.len()); +}