diff options
-rw-r--r-- | CHANGELOG.md | 12 | ||||
-rw-r--r-- | README.md | 4 | ||||
-rw-r--r-- | bzipper/Cargo.toml | 4 | ||||
-rw-r--r-- | bzipper/src/buffer/mod.rs | 126 | ||||
-rw-r--r-- | bzipper/src/buffer/test.rs | 36 | ||||
-rw-r--r-- | bzipper/src/fixed_string/mod.rs | 111 | ||||
-rw-r--r-- | bzipper/src/fixed_string/test.rs | 4 | ||||
-rw-r--r-- | bzipper/src/lib.rs | 3 | ||||
-rw-r--r-- | bzipper_macros/Cargo.toml | 2 |
9 files changed, 219 insertions, 83 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 00cd452..ff3b789 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,18 @@ # Changelog -This is the changelog of `bzipper`. +This is the changelog of bzipper. See `"README.md"` for more information. +## 0.6.0 + +* Update readme +* Add `Buffer` type +* Bump minor version +* Implement `PartialEq<&[char]>` for `FixedString` +* Update tests +* Implement `PartialOrd<&[char]>` and `PartialOrd<&str>` for `FixedString` +* Remove custom methods `get`, `get_unchecked`, `get_mut`, and `get_unchecked_mut`, `iter`, and `iter_mut` from `FixedString` + ## 0.5.2 * Respecify version numbers @@ -1,6 +1,6 @@ # bzipper -[`bzipper`](https://crates.io/crates/bzipper) is a binary (de)serialiser for the Rust language. +[bzipper](https://crates.io/crates/bzipper/) is a binary (de)serialiser for the Rust language. Contrary to [Serde](https://crates.io/crates/serde/)/[Bincode](https://crates.io/crates/bincode/), the goal of bzipper is to serialise with a known size constraint. Therefore, this crate may be more suited for networking or other cases where a fixed-sized buffer is needed. @@ -60,7 +60,7 @@ assert_eq!(buf, [0x00, 0x00, 0x04, 0x16]); The only special requirement of the `serialise` method is that the provided byte slice has an element count of exactly `SERIALISED_SIZE`. -We can also use streams to *chain* multiple elements together. +We can also use streams to *chain* multiple elements together: ```rs use bzipper::Serialise; diff --git a/bzipper/Cargo.toml b/bzipper/Cargo.toml index 08834ff..7e1babb 100644 --- a/bzipper/Cargo.toml +++ b/bzipper/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bzipper" -version = "0.5.2" +version = "0.6.0" edition = "2021" rust-version = "1.81" documentation = "https://docs.rs/bzipper/" @@ -20,7 +20,7 @@ alloc = [] std = [] [dependencies] -bzipper_macros = { path = "../bzipper_macros", version = "0.5.2"} +bzipper_macros = { path = "../bzipper_macros", version = "0.6.0"} [lints] workspace = true diff --git a/bzipper/src/buffer/mod.rs b/bzipper/src/buffer/mod.rs new file mode 100644 index 0000000..ad2e743 --- /dev/null +++ b/bzipper/src/buffer/mod.rs @@ -0,0 +1,126 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of bzipper. +// +// bzipper is free software: you can redistribute +// it and/or modify it under the terms of the GNU +// Lesser General Public License as published by +// the Free Software Foundation, either version 3 +// of the License, or (at your option) any later +// version. +// +// bzipper is distributed in the hope that it will +// be useful, but WITHOUT ANY WARRANTY; without +// even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Less- +// er General Public License along with bzipper. If +// not, see <https://www.gnu.org/licenses/>. + +#[cfg(test)] +mod test; + +use crate::{Deserialise, Result, Serialise}; + +use alloc::vec; +use alloc::boxed::Box; +use core::fmt::{Debug, Formatter}; +use core::marker::PhantomData; +use core::ops::{Deref, DerefMut}; + +/// Typed (de)serialisation buffer. +/// +/// This structure is intended as a lightweight wrapper around byte buffers for specific (de)serialisations of specific types. +/// +/// The methods [`write`](Self::write) and [`read`](Self::read) can be used to <interpreting> the internal buffer. +/// Other methods exist for accessing the internal buffer directly. +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +#[derive(Clone, Eq, PartialEq)] +pub struct Buffer<T: Serialise> { + buf: Box<[u8]>, + + _phanton: PhantomData<T> +} + +impl<T: Serialise> Buffer<T> { + /// Allocates a new buffer suitable for (de)serialisation. + #[must_use] + pub fn new() -> Self { Self { buf: vec![0x00; T::SERIALISED_SIZE].into(), _phanton: PhantomData } } + + /// Serialises into the contained buffer. + #[inline(always)] + pub fn write(&mut self, value: &T) -> Result<()> { value.serialise(&mut self.buf) } + + /// Retrieves a pointer to the first byte. + #[inline(always)] + #[must_use] + pub const fn as_ptr(&self) -> *const u8 { self.buf.as_ptr() } + + /// Retrieves a mutable pointer to the first byte. + #[inline(always)] + #[must_use] + pub fn as_mut_ptr(&mut self) -> *mut u8 { self.buf.as_mut_ptr() } + + /// Gets a slice of the intenral buffer. + #[inline(always)] + #[must_use] + pub const fn as_slice(&self) -> &[u8] { unsafe { core::slice::from_raw_parts(self.as_ptr(), self.len()) } } + + /// Gets a mutable slice of the intenral buffer. + #[inline(always)] + #[must_use] + pub fn as_mut_slice(&mut self) -> &mut [u8] { &mut self.buf } + + /// Gets the length of the buffer. + /// + /// This is defined as (and therefore always equal to) the value of [SERIALISED_SIZE](Serialise::SERIALISED_SIZE) as specified by `T`. + #[allow(clippy::len_without_is_empty)] + #[inline(always)] + #[must_use] + pub const fn len(&self) -> usize { T::SERIALISED_SIZE } +} + +impl<T: Deserialise> Buffer<T> { + /// Deserialises from the contained buffer. + #[inline(always)] + pub fn read(&self) -> Result<T> { T::deserialise(&self.buf) } +} + +impl<T: Serialise> AsMut<[u8]> for Buffer<T> { + #[inline(always)] + fn as_mut(&mut self) -> &mut [u8] { self.as_mut_slice() } +} + +impl<T: Serialise> AsRef<[u8]> for Buffer<T> { + #[inline(always)] + fn as_ref(&self) -> &[u8] { self.as_slice() } +} + +impl<T: Serialise> Debug for Buffer<T> { + #[inline(always)] + fn fmt(&self, f: &mut Formatter) -> core::fmt::Result { write!(f, "{:?}", self.as_slice()) } +} + +impl<T: Serialise> Default for Buffer<T> { + #[inline(always)] + fn default() -> Self { Self::new() } +} + +impl<T: Serialise> Deref for Buffer<T> { + type Target = [u8]; + + #[inline(always)] + fn deref(&self) -> &Self::Target { self.as_slice() } +} + +impl<T: Serialise> DerefMut for Buffer<T> { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { self.as_mut_slice() } +} + +impl<T: Serialise> PartialEq<&[u8]> for Buffer<T> { + #[inline(always)] + fn eq(&self, other: &&[u8]) -> bool { self.as_slice() == *other } +} diff --git a/bzipper/src/buffer/test.rs b/bzipper/src/buffer/test.rs new file mode 100644 index 0000000..4f24e45 --- /dev/null +++ b/bzipper/src/buffer/test.rs @@ -0,0 +1,36 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of bzipper. +// +// bzipper is free software: you can redistribute +// it and/or modify it under the terms of the GNU +// Lesser General Public License as published by +// the Free Software Foundation, either version 3 +// of the License, or (at your option) any later +// version. +// +// bzipper is distributed in the hope that it will +// be useful, but WITHOUT ANY WARRANTY; without +// even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Less- +// er General Public License along with bzipper. If +// not, see <https://www.gnu.org/licenses/>. + +use crate::{Buffer, Error}; + +#[test] +fn test_buffer() { + let mut buf = Buffer::<char>::new(); + + buf.write(&'\u{1F44D}').unwrap(); + assert_eq!(buf, [0x00, 0x01, 0xF4, 0x4D].as_slice()); + + buf.as_mut_slice().copy_from_slice(&[0x00, 0x00, 0xD8, 0x00]); + assert!(matches!(buf.read(), Err(Error::InvalidCodePoint { value: 0xD800 }))); + + buf.as_mut_slice().copy_from_slice(&[0x00, 0x00, 0xFF, 0x3A]); + assert_eq!(buf.read().unwrap(), '\u{FF3A}'); +} diff --git a/bzipper/src/fixed_string/mod.rs b/bzipper/src/fixed_string/mod.rs index 5b40a87..15c31aa 100644 --- a/bzipper/src/fixed_string/mod.rs +++ b/bzipper/src/fixed_string/mod.rs @@ -168,42 +168,6 @@ impl<const N: usize> FixedString<N> { self.len = len; } - /// Borrows characters at the specified index. - /// - /// If no element can be retrieved using the given index, [`None`] is returned instead. - #[inline(always)] - #[must_use] - pub fn get<I: SliceIndex<[char]>>(&self, index: I) -> Option<&I::Output> { self.buf.get(index) } - - /// Borrows characters at the specified index *without* checking bounds. - /// - /// For performing a similar operation *with* bounds checks, see [`get`](Self::get). - /// - /// # Safety - /// - /// If the given index points out of the bounds of the string, behaviour is undefined. - #[inline(always)] - #[must_use] - pub unsafe fn get_unchecked<I: SliceIndex<[char]>>(&self, index: I) -> &I::Output { self.buf.get_unchecked(index) } - - /// Mutably borrows characters at the specified index. - /// - /// If no element can be retrieved using the given index, [`None`] is returned instead. - #[inline(always)] - #[must_use] - pub fn get_mut<I: SliceIndex<[char]>>(&mut self, index: I) -> Option<&mut I::Output> { self.buf.get_mut(index) } - - /// Mutably borrows characters at the specified index *without* checking bounds. - /// - /// For performing a similar operation *with* bounds checks, see [`get_mut`](Self::get_mut) - /// - /// # Safety - /// - /// If the given index points out of the bounds of the string, behaviour is undefined. - #[inline(always)] - #[must_use] - pub unsafe fn get_unchecked_mut<I: SliceIndex<[char]>>(&mut self, index: I) -> &I::Output { self.buf.get_unchecked_mut(index) } - /// Pushes a character into the string. /// /// The internal length is updated accordingly. @@ -235,20 +199,6 @@ impl<const N: usize> FixedString<N> { c }) } - - /// Returns an iterator to the contained characters. - /// - /// This iterator only covers "used" character. - /// See [`iter_mut`](Self::iter_mut) for borrowing the entire buffer. - #[inline(always)] - pub fn iter(&self) -> core::slice::Iter<'_, char> { self.as_slice().iter() } - - /// Returns a mutable iterator to the contained characters. - /// - /// This iterator covers the entire internal buffer. - /// See [`iter`](Self::iter) for borrowing only "used" characters. - #[inline(always)] - pub fn iter_mut(&mut self) -> core::slice::IterMut<'_, char> { self.as_mut_slice().iter_mut() } } impl<const N: usize> AsMut<[char]> for FixedString<N> { @@ -377,16 +327,13 @@ impl<const N: usize> Ord for FixedString<N> { } impl<const N: usize, const M: usize> PartialEq<FixedString<M>> for FixedString<N> { - #[inline] - fn eq(&self, other: &FixedString<M>) -> bool { - if self.len() != other.len() { return false }; - - for i in 0x0..self.len() { - if self.buf[i] != other.buf[i] { return false }; - } + #[inline(always)] + fn eq(&self, other: &FixedString<M>) -> bool { self.as_slice() == other.as_slice() } +} - true - } +impl<const N: usize> PartialEq<&[char]> for FixedString<N> { + #[inline(always)] + fn eq(&self, other: &&[char]) -> bool { self.as_slice() == *other } } impl<const N: usize> PartialEq<&str> for FixedString<N> { @@ -401,24 +348,34 @@ impl<const N: usize> PartialEq<&str> for FixedString<N> { } impl<const N: usize, const M: usize> PartialOrd<FixedString<M>> for FixedString<N> { - fn partial_cmp(&self, other: &FixedString<M>) -> Option<Ordering> { - let len = self.len().max(other.len()); - - for i in 0x0..len { - let lc = self.get(i); - let rc = other.get(i); - - match (lc, rc) { - (None, None) => return Some(Ordering::Equal), - (Some(_), None) => return Some(Ordering::Greater), - (None, Some(_)) => return Some(Ordering::Less), - - (Some(lc), Some(rc)) => { - match lc.partial_cmp(rc) { - Some(Ordering::Equal) => {}, - ordering => return ordering - } - } + #[inline(always)] + fn partial_cmp(&self, other: &FixedString<M>) -> Option<Ordering> { self.partial_cmp(&other.as_slice()) } +} + +impl<const N: usize> PartialOrd<&[char]> for FixedString<N> { + #[inline(always)] + fn partial_cmp(&self, other: &&[char]) -> Option<Ordering> { self.as_slice().partial_cmp(other) } +} + +impl<const N: usize> PartialOrd<&str> for FixedString<N> { + #[inline] + fn partial_cmp(&self, other: &&str) -> Option<Ordering> { + let llen = self.len(); + let rlen = other.chars().count(); + + match llen.cmp(&rlen) { + Ordering::Equal => {}, + + ordering => return Some(ordering), + }; + + for (i, rc) in other.chars().enumerate() { + let lc = self[i]; + + match lc.cmp(&rc) { + Ordering::Equal => {}, + + ordering => return Some(ordering), } } diff --git a/bzipper/src/fixed_string/test.rs b/bzipper/src/fixed_string/test.rs index e2af6ce..c86a944 100644 --- a/bzipper/src/fixed_string/test.rs +++ b/bzipper/src/fixed_string/test.rs @@ -40,4 +40,8 @@ fn test_fixed_string() { assert_eq!(str2.partial_cmp(&str0), Some(Ordering::Less)); assert_eq!(str2.partial_cmp(&str1), Some(Ordering::Less)); assert_eq!(str2.partial_cmp(&str2), Some(Ordering::Equal)); + + assert_eq!(str0, "Hello there!"); + assert_eq!(str1, "MEIN_GRO\u{1E9E}_GOTT"); + assert_eq!(str2, "Hello"); } diff --git a/bzipper/src/lib.rs b/bzipper/src/lib.rs index 3689176..dd64693 100644 --- a/bzipper/src/lib.rs +++ b/bzipper/src/lib.rs @@ -200,3 +200,6 @@ use_mod!(pub fixed_iter); use_mod!(pub fixed_string); use_mod!(pub serialise); use_mod!(pub sstream); + +#[cfg(feature = "alloc")] +use_mod!(pub buffer); diff --git a/bzipper_macros/Cargo.toml b/bzipper_macros/Cargo.toml index a5c900f..695aefe 100644 --- a/bzipper_macros/Cargo.toml +++ b/bzipper_macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bzipper_macros" -version = "0.5.2" +version = "0.6.0" edition = "2021" documentation = "https://docs.rs/bzipper_macros/" |