diff --git a/CHANGELOG.md b/CHANGELOG.md index b46fe18..803fa5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,40 @@ # Changelog -This is the changelog of bzipper. +This is the changelog of Librum. See `README.md` for more information. +## 0.12.0 + +* Support custom errors in `Encode` and `Decode` (using associated `Error` type) +* Further split `EncodeError` into `IsizeEncodeError`, `UsizeEncodeError`, `CollectionEncodeError`, `RefCellEncodeError`, `ItemEncodeError`, and `EnumEncodeError` +* Fix the `Encode` implementation of `LinkedList` +* Further split `DecodeError` into `BoolDecodeError`, `CharDecodeError`, `CStringDecodeError`, `NonZeroDecodeError`, `CollectionDecodeError`, `SystemTimeDecodeError`, `EnumDecodeError`, and `ItemDecodeError` +* Honour custom discriminant types +* Add `DecodeBorrowed` trait (implement appropriately) +* Implement `Decode` for `Cow` +* Refactor derive macros +* Update lints +* Rename test modules from `test` to `tests` +* Restructure some trait implementations +* Add `proc-macro` feature flag +* Add `GenericEncodeError` and `GenericDecodeError` error types for derived traits +* Add `PrimitiveDiscriminant` trait +* Lock `Arc` implementations behind atomic widths +* Add `?Sized` clauses to some `Encode` implementations +* Update readme +* Fix doc entry for `SizedStr::new` +* Update atomic tests +* Make `SizedEncode` a safe trait +* Do not automatically implement `Encode` when deriving `SizedEncode` +* Add `copy_from_slice` method to `SizedSlice` +* Add `each_ref` and `each_mut` methods to `SizedSlice` +* Add more benchmarks +* Remove Ciborium benchmarks +* Rename project to *Librum* +* Rename `bzipper` crate to `librum` +* Rename `bzipper_macros` crate to `librum-macros` +* Rename `bzipper_benchmarks` crate to `librum-benchmarks` + ## 0.11.0 * Add `into_bytes` destructor to `SizedStr` diff --git a/Cargo.toml b/Cargo.toml index 99c1dde..ca07297 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["bzipper", "bzipper_benchmarks", "bzipper_macros"] +members = ["librum", "librum-benchmarks", "librum-macros"] resolver = "2" [workspace.package] @@ -7,14 +7,14 @@ authors = ["Gabriel Bjørnager Jensen"] description = "Binary (de)serialiser." readme = "README.md" homepage = "https://achernar.dk/index.php?p=bzipper" -repository = "https://mandelbrot.dk/bzipper/" +repository = "https://mandelbrot.dk/librum/" license = "LGPL-3.0-or-later" keywords = ["api", "encoding", "io", "network", "no-std"] categories = ["encoding", "network-programming", "parsing"] [workspace.lints.clippy] as_ptr_cast_mut = "forbid" -as_underscore = "warn" +as_underscore = "forbid" assertions_on_result_states = "warn" bool_to_int_with_if = "warn" borrow_as_ptr = "forbid" @@ -90,8 +90,6 @@ mutex_integer = "deny" needless_bitwise_bool = "deny" needless_collect = "warn" needless_continue = "warn" -needless_pass_by_ref_mut = "warn" -needless_pass_by_value = "deny" needless_raw_string_hashes = "warn" needless_raw_strings = "warn" no_effect_underscore_binding = "deny" @@ -111,7 +109,6 @@ read_zero_byte_vec = "deny" redundant_clone = "deny" redundant_closure_for_method_calls = "warn" redundant_else = "warn" -redundant_pub_crate = "warn" redundant_type_annotations = "warn" ref_as_ptr = "deny" ref_binding_to_reference = "warn" @@ -142,7 +139,6 @@ unnested_or_patterns = "warn" unused_async = "warn" unused_peekable = "warn" unused_rounding = "warn" -unused_self = "warn" use_self = "deny" used_underscore_binding = "deny" useless_let_if_seq = "warn" diff --git a/README.md b/README.md index af3a3b5..d7d48af 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# bZipper +# Librum -bZipper is a Rust crate for cheaply serialising (encoding) and deserialising (decoding) data structures into binary streams +Librum is a Rust crate for cheaply serialising (encoding) and deserialising (decoding) data structures into binary streams What separates this crate from others such as [Bincode](https://crates.io/crates/bincode/) or [Postcard](https://crates.io/crates/postcard/) is that this crate is extensively optimised for *just* binary encodings (whilst the mentioned crates specifically use Serde and build on a more abstract data model). The original goal of this project was specifically to guarantee size constraints for encodings on a per-type basis at compile-time. @@ -13,81 +13,102 @@ This crate is compatible with `no_std`. ## Performance -As bZipper is optimised exclusively for a single, binary format, it may outperform other libraries that are more generic in nature. +As Librum is optimised exclusively for a single, binary format, it may outperform other libraries that are more generic in nature. -The `bzipper_benchmarks` binary compares multiple scenarios using bZipper and other, similar crates. -According to my runs on an AMD Ryzen 7 3700X, these benchmarks indicate that bZipper outperform all of the tested crates -- as demonstrated in the following table: +The `librum-benchmarks` binary compares multiple scenarios using Librum and other, similar crates. +According to my runs on an AMD Ryzen 7 3700X, these benchmarks indicate that Librum outperform all of the tested crates -- as demonstrated in the following table: -| Benchmark | [Bincode] | [Borsh] | bZipper | [Ciborium] | [Postcard] | -| :--------------------------------- | --------: | ------: | ------: | ---------: | ---------: | -| `encode_u8` | 1.234 | 1.096 | 0.881 | 3.076 | 1.223 | -| `encode_struct_unit` | 0.000 | 0.000 | 0.000 | 0.516 | 0.000 | -| `encode_struct_unnamed` | 1.367 | 1.154 | 1.009 | 2.051 | 1.191 | -| `encode_struct_named` | 4.101 | 1.271 | 1.181 | 9.342 | 1.182 | -| `encode_enum_unit` | 0.306 | 0.008 | 0.000 | 2.304 | 0.004 | -| **Total time** → | 7.009 | 3.528 | 3.071 | 17.289 | 3.599 | -| **Total deviation (p.c.)** → | +128 | +15 | ±0 | +463 | +17 | +| Benchmark | [Bincode] | [Borsh] | Librum | [Postcard] | +| :--------------------------------- | --------: | ------: | ------: | ---------: | +| `encode_u8` | 1.306 | 1.315 | 1.150 | 1.304 | +| `encode_u32` | 1.321 | 1.317 | 1.146 | 3.016 | +| `encode_u128` | 2.198 | 2.103 | 1.509 | 6.376 | +| `encode_struct_unit` | 0.000 | 0.000 | 0.000 | 0.000 | +| `encode_struct_unnamed` | 1.362 | 1.448 | 1.227 | 2.659 | +| `encode_struct_named` | 3.114 | 1.530 | 0.969 | 3.036 | +| `encode_enum_unit` | 0.252 | 0.297 | 0.000 | 0.299 | +| **Total time** → | 9.553 | 8.010 | 6.001 | 16.691 | +| **Total deviation (p.c.)** → | +59 | +33 | ±0 | +178 | [Bincode]: https://crates.io/crates/bincode/ [Borsh]: https://crates.io/crates/borsh/ -[Ciborium]: https://crates.io/crates/ciborium/ [Postcard]: https://crates.io/crates/postcard/ All quantities are measured in seconds unless otherwise noted. -Please feel free to conduct your own tests of bZipper. +Please feel free to conduct your own tests of Librum. ## Data model -Most primitives encode losslessly, with the main exceptions being `usize` and `isize`. -These are instead first cast as `u16` and `i16`, respectively, due to portability concerns (with respect to embedded systems). +Most primitives encode losslessly, with the main exceptions being [`usize`] and [`isize`]. +These are instead first cast as [`u16`] and [`i16`], respectively, due to portability concerns (with respect to embedded systems). See specific types' implementations for notes on their data models. -**Note that the data model is currently not stabilised,** and may not necessarily be in the near future (before [specialisation](https://github.com/rust-lang/rust/issues/31844/)). +**Note that the data model is currently not stabilised,** and may not necessarily be in the near future (at least before [specialisation](https://github.com/rust-lang/rust/issues/31844/)). It may therefore be undesired to store encodings long-term. ## Usage -This crate revolves around the `Encode` and `Decode` traits which both handle conversions to and from byte streams. +This crate revolves around the [`Encode`] and [`Decode`] traits which both handle conversions to and from byte streams. -Many standard types come implemented with bZipper, including most primitives as well as some standard library types such as `Option` and `Result`. -Some [features](#features-flags) enable an extended set of implementations. +Many standard types come implemented with Librum, including most primitives as well as some standard library types such as [`Option`] and [`Result`]. +Some [features](#feature-flags) enable an extended set of implementations. -It is recommended in most cases to simply derive these two traits for custom types (although this is only supported with enumerations and structures – not untagged unions). +It is recommended in most cases to simply derive these two traits for custom types (although this is only supported with enumerations and structures -- not untagged unions). Here, each field is *chained* according to declaration order: -```rust -use bzipper::{Buf, Decode, Encode, SizedEncode}; +``` +use librum::{Buf, Decode, Encode}; -#[derive(Debug, Decode, PartialEq, SizedEncode)] -struct IoRegister { - addr: u32, - value: u16, +##[derive(Debug, Decode, Encode, PartialEq)] +struct Ints { + value0: u8, + value1: u16, + value2: u32, + value3: u64, + value4: u128, } -let mut buf = Buf::new(); +const VALUE: Ints = Ints { + value0: 0x00, + value1: 0x02_01, + value2: 0x06_05_04_03, + value3: 0x0E_0D_0C_0B_0A_09_08_07, + value4: 0x1E_1D_1C_1B_1A_19_18_17_16_15_14_13_12_11_10_0F, +}; -buf.write(IoRegister { addr: 0x04000000, value: 0x0402 }).unwrap(); +let mut buf = Buf::with_capacity(0x100); -assert_eq!(buf.len(), 0x6); -assert_eq!(buf, [0x04, 0x00, 0x00, 0x00, 0x04, 0x02].as_slice()); +buf.write(VALUE).unwrap(); -assert_eq!(buf.read().unwrap(), IoRegister { addr: 0x04000000, value: 0x0402 }); +assert_eq!(buf.len(), 0x1F); + +assert_eq!( + buf, + [ + 0x00, 0x02, 0x01, 0x06, 0x05, 0x04, 0x03, 0x0E, + 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x1E, + 0x1D, 0x1C, 0x1B, 0x1A, 0x19, 0x18, 0x17, 0x16, + 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x0F, + ].as_slice(), +); + +assert_eq!(buf.read().unwrap(), VALUE); ``` ### Buffer types -The `Encode` and `Decode` traits both rely on streams for carrying the manipulated byte streams. +The [`Encode`] and [`Decode`] traits both rely on streams for carrying the manipulated bytes. -These streams are separated into two type: *O-streams* (output streams) and *i-streams* (input streams). -Often, but not always, the `Buf` type is preferred over directly calling the `encode` and `decode` methods. +These streams are separated into two type: [*O-streams*](OStream) (output streams) and [*i-streams*](IStream) (input streams). +The [`Buf`] type can be used to handle these streams. ### Encoding -To encode an object directly using the `Encode` trait, simply allocate a buffer for the encoding and wrap it in an `OStream` object: +To encode an object directly using the `Encode` trait, simply allocate a buffer for the encoding and wrap it in an [`OStream`] object: -```rust -use bzipper::{Encode, OStream, SizedEncode}; +``` +use librum::{Encode, OStream, SizedEncode}; let mut buf = [0x00; char::MAX_ENCODED_SIZE]; let mut stream = OStream::new(&mut buf); @@ -99,8 +120,8 @@ assert_eq!(buf, [0x00, 0x00, 0x04, 0x16].as_slice()); Streams can also be used to chain multiple objects together: -```rust -use bzipper::{Encode, OStream, SizedEncode}; +``` +use librum::{Encode, OStream, SizedEncode}; let mut buf = [0x0; char::MAX_ENCODED_SIZE * 0x5]; let mut stream = OStream::new(&mut buf); @@ -122,7 +143,7 @@ assert_eq!(buf, [ ]); ``` -If the encoded type additionally implements `SizedEncode`, then the maximum size of any encoding is guaranteed with the `MAX_ENCODED_SIZE` constant. +If the encoded type additionally implements [`SizedEncode`], then the maximum size of any encoding is guaranteed with the [`MAX_ENCODED_SIZE`](SizedEncode::MAX_ENCODED_SIZE) constant. Numerical primitives are encoded in big endian (a.k.a. [network order](https://en.wikipedia.org/wiki/Endianness#Networking)) for... reasons. It is recommended for implementors to follow this convention as well. @@ -130,10 +151,10 @@ It is recommended for implementors to follow this convention as well. ### Decoding Decoding works with a similar syntax to encoding. -To decode a byte array, simply call the `decode` method with an `IStream` object: +To decode a byte array, simply call the [`decode`](Decode::decode) method with an [`IStream`] object: -```rust -use bzipper::{Decode, IStream}; +``` +use librum::{Decode, IStream}; let data = [0x45, 0x54]; let mut stream = IStream::new(&data); @@ -158,14 +179,14 @@ assert_eq!(<(u8, u8)>::decode(&mut stream).unwrap(), (0x45, 0x54)); A UDP server/client for geographic data: -```rust -use bzipper::{Buf, Decode, SizedEncode}; +``` +use librum::{Buf, Encode, Decode, SizedEncode}; use std::io; use std::net::{SocketAddr, ToSocketAddrs, UdpSocket}; use std::thread::spawn; // City, region, etc.: -#[derive(Clone, Copy, Debug, Decode, Eq, PartialEq, SizedEncode)] +##[derive(Clone, Copy, Debug, Decode, Encode, Eq, PartialEq, SizedEncode)] enum Area { AlQuds, Byzantion, @@ -175,7 +196,7 @@ enum Area { } // Client-to-server message: -#[derive(Debug, Decode, PartialEq, SizedEncode)] +##[derive(Debug, Decode, Encode, PartialEq, SizedEncode)] enum Request { AtmosphericHumidity { area: Area }, AtmosphericPressure { area: Area }, @@ -184,7 +205,7 @@ enum Request { } // Server-to-client message: -#[derive(Debug, Decode, PartialEq, SizedEncode)] +##[derive(Debug, Decode, Encode, PartialEq, SizedEncode)] enum Response { AtmosphericHumidity(f64), AtmosphericPressure(f64), // Pascal @@ -260,25 +281,28 @@ spawn(move || { ## Feature flags -bZipper defines the following features: +Librum defines the following features: -* `alloc` (default): Enables the `Buf` type and implementations for e.g. `Box` and `Arc` -* `std` (default): Enables implementations for types such as `Mutex` and `RwLock` +* *`alloc`: Enables the [`Buf`] type and implementations for e.g. [`Box`](alloc::boxed::Box) and [`Arc`](alloc::sync::Arc) +* *`proc-macro`: Pulls the procedural macros from the [`librum_macros`](https://crates.io/crates/librum_macros/) crate +* *`std`: Enables implementations for types such as [`Mutex`](std::sync::Mutex) and [`RwLock`](std::sync::RwLock) + +Features marked with * are enabled by default. ## Documentation -bZipper has its documentation written in-source for use by `rustdoc`. -See [Docs.rs](https://docs.rs/bzipper/latest/bzipper/) for an on-line, rendered instance. +Librum has its documentation written in-source for use by `rustdoc`. +See [Docs.rs](https://docs.rs/librum/latest/librum/) for an on-line, rendered instance. Currently, these docs make use of some unstable features for the sake of readability. The nightly toolchain is therefore required when rendering them. ## Contribution -bZipper does not accept source code contributions at the moment. +Librum does not accept source code contributions at the moment. This is a personal choice by the maintainer and may be undone in the future. -Do however feel free to open up an issue on [`GitLab`](https://gitlab.com/bjoernager/bzipper/issues/) or (preferably) [`GitHub`](https://github.com/bjoernager/bzipper/issues/) if you feel the need to express any concerns over the project. +Do however feel free to open up an issue on [`GitLab`](https://gitlab.com/bjoernager/librum/issues/) or (preferably) [`GitHub`](https://github.com/bjoernager/librum/issues/) if you feel the need to express any concerns over the project. ## Copyright & Licence @@ -290,4 +314,4 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. -If not, see . +If not, see . \ No newline at end of file diff --git a/bzipper/src/error/decode_error/mod.rs b/bzipper/src/error/decode_error/mod.rs deleted file mode 100644 index 28478f6..0000000 --- a/bzipper/src/error/decode_error/mod.rs +++ /dev/null @@ -1,139 +0,0 @@ -// 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 . - -use crate::error::{SizeError, Utf8Error}; - -use core::error::Error; -use core::fmt::{self, Display, Formatter}; - -#[cfg(feature = "alloc")] -use alloc::boxed::Box; - -/// Decode error variants. -/// -/// These errors may be returned from implementation of [`Decode`](crate::Decode). -#[derive(Debug)] -#[non_exhaustive] -pub enum DecodeError { - /// Bytes were requested on an empty stream. - /// - /// This variant is different from [`SmallBuffer`](Self::SmallBuffer) in that this is exclusively for use by the stream types, whilst `SmallBuffer` is for any other array-like type. - BadString(Utf8Error), - - /// An unspecified error. - /// - /// This is mainly useful by third-party implementors if none of the other predefined variants are appropriate. - #[cfg(feature = "alloc")] - #[cfg_attr(doc, doc(cfg(feature = "alloc")))] - CustomError(Box), - - /// A boolean encountered a value outside `0` and `1`. - InvalidBoolean(u8), - - /// An invalid code point was encountered. - /// - /// This includes surrogate points in the inclusive range `U+D800` to `U+DFFF`, as well as all values larger than `U+10FFFF`. - InvalidCodePoint(u32), - - /// An invalid enumeration descriminant was provided. - InvalidDiscriminant(isize), - - /// The [`SystemTime`](std::time::SystemTime) type could not represent a UNIX timestamp. - /// - /// This error should not occur on systems that represent timestamps with at least a signed 64-bits seconds counter. - #[cfg(feature = "std")] - #[cfg_attr(doc, doc(cfg(feature = "std")))] - NarrowSystemTime { - /// The unrepresentable timestamp. - timestamp: i64, - }, - - /// A non-zero integer had the value `0`. - NullInteger, - - /// A C-like string encountered a null value within bounds. - #[cfg(feature = "alloc")] - #[cfg_attr(doc, doc(cfg(feature = "alloc")))] - NullCString { - /// The index of the null value. - index: usize, - }, - - /// An array could not hold the requested amount of elements. - SmallBuffer(SizeError), -} - -impl Display for DecodeError { - #[inline] - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - use DecodeError::*; - - match *self { - BadString(ref source) - => write!(f, "bad string: {source}"), - - #[cfg(feature = "alloc")] - CustomError(ref source) - => write!(f, "{source}"), - - InvalidBoolean(value) - => write!(f, "expected boolean but got `{value:#02X}`"), - - InvalidCodePoint(value) - => write!(f, "code point U+{value:04X} is not defined"), - - InvalidDiscriminant(value) - => write!(f, "discriminant `{value}` is not valid for the given enumeration"), - - #[cfg(feature = "std")] - NarrowSystemTime { timestamp } - => write!(f, "could not represent `{timestamp}` as a system timestamp"), - - NullInteger - => write!(f, "expected non-zero integer but got `0`"), - - #[cfg(feature = "alloc")] - NullCString { index } - => write!(f, "expected c string but found null value at '{index}`"), - - SmallBuffer(ref source) - => write!(f, "buffer too small: {source}"), - } - } -} - -impl Error for DecodeError { - #[inline] - fn source(&self) -> Option<&(dyn Error + 'static)> { - use DecodeError::*; - - match *self { - BadString(ref source) => Some(source), - - #[cfg(feature = "alloc")] - CustomError(ref source) => Some(source.as_ref()), - - SmallBuffer(ref source) => Some(source), - - _ => None, - } - } -} diff --git a/bzipper/src/error/encode_error/mod.rs b/bzipper/src/error/encode_error/mod.rs deleted file mode 100644 index 4a9d0d2..0000000 --- a/bzipper/src/error/encode_error/mod.rs +++ /dev/null @@ -1,89 +0,0 @@ -// 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 . - -use core::cell::BorrowError; -use core::error::Error; -use core::fmt::{self, Display, Formatter}; - -#[cfg(feature = "alloc")] -use alloc::boxed::Box; - -/// Encode error variants. -/// -/// These errors may be returned from implementation of [`Encode`](crate::Encode). -#[derive(Debug)] -#[non_exhaustive] -pub enum EncodeError { - /// A [`RefCell`](core::cell::RefCell) object could not be borrowed. - BadBorrow(BorrowError), - - /// An unspecified error. - /// - /// This is mainly useful by third-party implementors if none of the other predefined variants are appropriate. - #[cfg(feature = "alloc")] - #[cfg_attr(doc, doc(cfg(feature = "alloc")))] - CustomError(Box), - - /// An `isize` value could not be cast as `i16`. - IsizeOutOfRange(isize), - - /// A `usize` value could not be cast as `u16`. - UsizeOutOfRange(usize), -} - -impl Display for EncodeError { - #[inline] - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - use EncodeError::*; - - match *self { - BadBorrow(ref source) - => write!(f, "could not borrow reference cell: {source}"), - - #[cfg(feature = "alloc")] - CustomError(ref source) - => write!(f, "{source}"), - - IsizeOutOfRange(value) - => write!(f, "signed size value ({value}) cannot be serialised: must be in the range ({}) to ({})", i16::MIN, i16::MAX), - - UsizeOutOfRange(value) - => write!(f, "unsigned size value ({value}) cannot be serialised: must be at most ({})", u16::MAX), - } - } -} - -impl Error for EncodeError { - #[inline] - fn source(&self) -> Option<&(dyn Error + 'static)> { - use EncodeError::*; - - match *self { - // In practice useless. - BadBorrow(ref source) => Some(source), - - #[cfg(feature = "alloc")] - CustomError(ref source) => Some(source.as_ref()), - - _ => None, - } - } -} diff --git a/bzipper/src/error/mod.rs b/bzipper/src/error/mod.rs deleted file mode 100644 index a46ccf1..0000000 --- a/bzipper/src/error/mod.rs +++ /dev/null @@ -1,34 +0,0 @@ -// 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 . - -//! Error variants. -//! -//! This module defines the error types used by bZipper. -//! All of these types define the [`Error`](core::error::Error) trait. - -use crate::use_mod; - -use_mod!(pub decode_error); -use_mod!(pub encode_error); -use_mod!(pub size_error); -use_mod!(pub string_error); -use_mod!(pub utf16_error); -use_mod!(pub utf8_error); diff --git a/bzipper/src/sized_slice/mod.rs b/bzipper/src/sized_slice/mod.rs deleted file mode 100644 index 2f880c3..0000000 --- a/bzipper/src/sized_slice/mod.rs +++ /dev/null @@ -1,552 +0,0 @@ -// 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 . - -#[cfg(test)] -mod test; - -use crate::{ - Decode, - Encode, - IStream, - OStream, - SizedEncode, - SizedIter, -}; -use crate::error::{DecodeError, EncodeError, SizeError}; - -use core::borrow::{Borrow, BorrowMut}; -use core::cmp::Ordering; -use core::fmt::{self, Debug, Formatter}; -use core::hash::{Hash, Hasher}; -use core::mem::MaybeUninit; -use core::ops::{Deref, DerefMut, Index, IndexMut}; -use core::ptr::copy_nonoverlapping; -use core::slice; -use core::slice::{Iter, IterMut, SliceIndex}; - -#[cfg(feature = "alloc")] -use alloc::alloc::{alloc, Layout}; - -#[cfg(feature = "alloc")] -use alloc::boxed::Box; - -#[cfg(feature = "alloc")] -use alloc::vec::Vec; - -/// Stack-allocated vector with maximum length. -/// -/// This type is intended as an [sized-encodable](SizedEncode) alternative to [`Vec`] for cases where [arrays](array) may not be wanted. -/// -/// Note that this type is immutable in the sense that it does **not** define methods like `push` and `pop`, unlike `Vec`. -/// -/// See [`SizedStr`](crate::SizedStr) for an equivalent alternative to [`String`](alloc::string::String). -/// -/// # Examples -/// -/// All instances of this type with the same `T` and `N` also have the exact same layout: -/// -/// ``` -/// use bzipper::SizedSlice; -/// -/// let vec0 = SizedSlice::::try_from([0x3].as_slice()).unwrap(); -/// let vec1 = SizedSlice::::try_from([0x3, 0x2].as_slice()).unwrap(); -/// let vec2 = SizedSlice::::try_from([0x3, 0x2, 0x4].as_slice()).unwrap(); -/// let vec3 = SizedSlice::::try_from([0x3, 0x2, 0x4, 0x3].as_slice()).unwrap(); -/// -/// assert_eq!(size_of_val(&vec0), size_of_val(&vec1)); -/// assert_eq!(size_of_val(&vec0), size_of_val(&vec2)); -/// assert_eq!(size_of_val(&vec0), size_of_val(&vec3)); -/// assert_eq!(size_of_val(&vec1), size_of_val(&vec2)); -/// assert_eq!(size_of_val(&vec1), size_of_val(&vec3)); -/// assert_eq!(size_of_val(&vec2), size_of_val(&vec3)); -/// ``` -pub struct SizedSlice { - buf: [MaybeUninit; N], - len: usize, -} - -impl SizedSlice { - /// Constructs a fixed-size vector from raw parts. - /// - /// The provided parts are not tested in any way. - /// - /// # Safety - /// - /// The value of `len` may not exceed that of `N`. - /// Additionally, all elements of `buf` in the range specified by `len` must be initialised. - /// - /// If any of these requirements are violated, behaviour is undefined. - #[inline(always)] - #[must_use] - pub const unsafe fn from_raw_parts(buf: [MaybeUninit; N], len: usize) -> Self { - debug_assert!(len <= N, "cannot construct vector longer than its capacity"); - - Self { buf, len } - } - - /// Sets the length of the vector. - /// - /// The provided length is not tested in any way. - /// - /// # Safety - /// - /// The new length `len` may not be larger than `N`. - /// - /// It is only valid to enlarge vectors if `T` supports being in a purely uninitialised state. - /// Such is permitted with e.g. [`MaybeUninit`]. - #[inline(always)] - pub const unsafe fn set_len(&mut self, len: usize) { - debug_assert!(len <= N, "cannot set length past bounds"); - - self.len = len - } - - /// Gets a pointer to the first element. - /// - /// The pointed-to element may not necessarily be initialised. - /// See [`len`](Self::len) for more information. - #[inline(always)] - #[must_use] - pub const fn as_ptr(&self) -> *const T { - self.buf.as_ptr().cast() - } - - /// Gets a mutable pointer to the first element. - /// - /// The pointed-to element may not necessarily be initialised. - /// See [`len`](Self::len) for more information. - #[inline(always)] - #[must_use] - pub const fn as_mut_ptr(&mut self) -> *mut T { - self.buf.as_mut_ptr().cast() - } - - /// Borrows the vector as a slice. - /// - /// The range of the returned slice only includes the elements specified by [`len`](Self::len). - #[inline(always)] - #[must_use] - pub const fn as_slice(&self) -> &[T] { - let ptr = self.as_ptr(); - let len = self.len(); - - unsafe { slice::from_raw_parts(ptr, len) } - } - - /// Borrows the vector as a mutable slice. - /// - /// The range of the returned slice only includes the elements specified by [`len`](Self::len). - #[inline(always)] - #[must_use] - pub const fn as_mut_slice(&mut self) -> &mut [T] { - let ptr = self.as_mut_ptr(); - let len = self.len(); - - unsafe { slice::from_raw_parts_mut(ptr, len) } - } - - /// Returns the total capacity of the vector. - /// - /// By definition, this is always exactly equal to the value of `N`. - #[expect(clippy::unused_self)] - #[inline(always)] - #[must_use] - pub const fn capacity(&self) -> usize { - N - } - - /// Returns the length of the vector. - /// - /// This value may necessarily be smaller than `N`. - #[inline(always)] - #[must_use] - pub const fn len(&self) -> usize { - self.len - } - - /// Checks if the vector is empty, i.e. no elements are recorded. - /// - /// Note that the internal buffer may still contain objects that have been "shadowed" by setting a smaller length with [`len`](Self::len). - #[inline(always)] - #[must_use] - pub const fn is_empty(&self) -> bool { - self.len() == 0x0 - } - - /// Checks if the vector is full, i.e. it cannot hold any more elements. - #[inline(always)] - #[must_use] - pub const fn is_full(&self) -> bool { - self.len() == self.capacity() - } - - /// Destructs the vector into its raw parts. - /// - /// The returned values are valid to pass on to [`from_raw_parts`](Self::from_raw_parts). - #[inline(always)] - #[must_use] - pub const fn into_raw_parts(self) -> ([MaybeUninit; N], usize) { - let Self { buf, len } = self; - (buf, len) - } - - /// Converts the vector into a boxed slice. - /// - /// The vector is reallocated using the global allocator. - #[cfg(feature = "alloc")] - #[cfg_attr(doc, doc(cfg(feature = "alloc")))] - #[must_use] - pub fn into_boxed_slice(self) -> Box<[T]> { - let (buf, len) = self.into_raw_parts(); - - unsafe { - let layout = Layout::array::(len).unwrap(); - let ptr = alloc(layout).cast::(); - - assert!(!ptr.is_null(), "allocation failed"); - - copy_nonoverlapping(buf.as_ptr().cast(), ptr, len); - - let slice = core::ptr::slice_from_raw_parts_mut(ptr, len); - Box::from_raw(slice) - - // `self.buf` is dropped without destructors being - // run. - } - } - - /// Converts the vector into a dynamic vector. - /// - /// The vector is reallocated using the global allocator. - #[cfg(feature = "alloc")] - #[cfg_attr(doc, doc(cfg(feature = "alloc")))] - #[inline(always)] - #[must_use] - pub fn into_vec(self) -> Vec { - self.into_boxed_slice().into_vec() - } -} - -impl SizedSlice { - /// Constructs an empty, fixed-size vector. - #[inline] - pub fn new(data: &[T]) -> Result { - let mut buf: [MaybeUninit; N] = unsafe { MaybeUninit::uninit().assume_init() }; - - let len = data.len(); - if len > N { return Err(SizeError { req: len, len: N }) }; - - for (item, value) in buf.iter_mut().zip(data.iter()) { - item.write(value.clone()); - } - - Ok(Self { buf, len }) - } -} - -impl AsMut<[T]> for SizedSlice { - #[inline(always)] - fn as_mut(&mut self) -> &mut [T] { - self.as_mut_slice() - } -} - -impl AsRef<[T]> for SizedSlice { - #[inline(always)] - fn as_ref(&self) -> &[T] { - self.as_slice() - } -} - -impl Borrow<[T]> for SizedSlice { - #[inline(always)] - fn borrow(&self) -> &[T] { - self.as_slice() - } -} - -impl BorrowMut<[T]> for SizedSlice { - #[inline(always)] - fn borrow_mut(&mut self) -> &mut [T] { - self.as_mut_slice() - } -} - -impl Clone for SizedSlice { - #[inline] - fn clone(&self) -> Self { - unsafe { - let mut buf: [MaybeUninit; N] = MaybeUninit::uninit().assume_init(); - - for i in 0x0..self.len() { - let value = self.get_unchecked(i).clone(); - buf.get_unchecked_mut(i).write(value); - } - - Self { buf, len: self.len } - } - } -} - -impl Debug for SizedSlice { - #[inline(always)] - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - Debug::fmt(self.as_slice(), f) - } -} - -impl Default for SizedSlice { - #[inline(always)] - fn default() -> Self { - Self { buf: unsafe { MaybeUninit::uninit().assume_init() }, len: 0x0 } - } -} - -impl Deref for SizedSlice { - type Target = [T]; - - #[inline(always)] - fn deref(&self) -> &Self::Target { - self.as_slice() - } -} - -impl DerefMut for SizedSlice { - #[inline(always)] - fn deref_mut(&mut self) -> &mut Self::Target { - self.as_mut_slice() - } -} - -impl Decode for SizedSlice { - #[inline] - fn decode(stream: &mut IStream) -> Result { - let len = Decode::decode(stream)?; - if len > N { return Err(DecodeError::SmallBuffer(SizeError { req: len, len: N })) }; - - let mut buf: [MaybeUninit; N] = unsafe { MaybeUninit::uninit().assume_init() }; - - for item in &mut buf { - let value = Decode::decode(stream)?; - - item.write(value); - } - - Ok(Self { buf, len }) - } -} - -impl Encode for SizedSlice { - #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { - self.as_slice().encode(stream) - } -} - -impl Eq for SizedSlice { } - -impl From<[T; N]> for SizedSlice { - #[inline(always)] - fn from(value: [T; N]) -> Self { - unsafe { - let buf = value.as_ptr().cast::<[MaybeUninit; N]>().read(); - - Self { buf, len: N } - } - } -} - -impl FromIterator for SizedSlice { - #[inline] - fn from_iter>(iter: I) -> Self { - let mut iter = iter.into_iter(); - - let mut buf: [MaybeUninit; N] = unsafe { MaybeUninit::uninit().assume_init() }; - let mut len = 0x0; - - for item in &mut buf { - let Some(value) = iter.next() else { break }; - item.write(value); - - len += 0x1; - } - - Self { buf, len } - } -} - -impl Hash for SizedSlice { - #[inline(always)] - fn hash(&self, state: &mut H) { - for v in self { - v.hash(state); - } - } -} - -impl, const N: usize> Index for SizedSlice { - type Output = I::Output; - - #[inline(always)] - fn index(&self, index: I) -> &Self::Output { - self.get(index).unwrap() - } -} - -impl, const N: usize> IndexMut for SizedSlice { - #[inline(always)] - fn index_mut(&mut self, index: I) -> &mut Self::Output { - self.get_mut(index).unwrap() - } -} - -impl IntoIterator for SizedSlice { - type Item = T; - - type IntoIter = SizedIter; - - #[inline(always)] - fn into_iter(self) -> Self::IntoIter { - let Self { buf, len } = self; - - unsafe { SizedIter::new(buf, len) } - } -} - -impl<'a, T, const N: usize> IntoIterator for &'a SizedSlice { - type Item = &'a T; - - type IntoIter = Iter<'a, T>; - - #[inline(always)] - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -impl<'a, T, const N: usize> IntoIterator for &'a mut SizedSlice { - type Item = &'a mut T; - - type IntoIter = IterMut<'a, T>; - - #[inline(always)] - fn into_iter(self) -> Self::IntoIter { - self.iter_mut() - } -} - -impl Ord for SizedSlice { - #[inline(always)] - fn cmp(&self, other: &Self) -> Ordering { - self.as_slice().cmp(other.as_slice()) - } -} - -impl, U: PartialEq, const N: usize, const M: usize> PartialEq> for SizedSlice { - #[inline(always)] - fn eq(&self, other: &SizedSlice) -> bool { - self.as_slice() == other.as_slice() - } -} - -impl, U: PartialEq, const N: usize, const M: usize> PartialEq<[U; M]> for SizedSlice { - #[inline(always)] - fn eq(&self, other: &[U; M]) -> bool { - self.as_slice() == other.as_slice() - } -} - -impl, U: PartialEq, const N: usize> PartialEq<&[U]> for SizedSlice { - #[inline(always)] - fn eq(&self, other: &&[U]) -> bool { - self.as_slice() == *other - } -} - -#[cfg(feature = "alloc")] -#[cfg_attr(doc, doc(cfg(feature = "alloc")))] -impl, U: PartialEq, const N: usize> PartialEq> for SizedSlice { - #[inline(always)] - fn eq(&self, other: &Vec) -> bool { - self.as_slice() == other.as_slice() - } -} - -impl PartialOrd> for SizedSlice { - #[inline(always)] - fn partial_cmp(&self, other: &SizedSlice) -> Option { - self.as_slice().partial_cmp(other.as_slice()) - } -} - -impl PartialOrd<[T; M]> for SizedSlice { - #[inline(always)] - fn partial_cmp(&self, other: &[T; M]) -> Option { - self.as_slice().partial_cmp(other.as_slice()) - } -} - -impl PartialOrd<&[T]> for SizedSlice { - #[inline(always)] - fn partial_cmp(&self, other: &&[T]) -> Option { - self.as_slice().partial_cmp(*other) - } -} - -#[cfg(feature = "alloc")] -#[cfg_attr(doc, doc(cfg(feature = "alloc")))] -impl PartialOrd> for SizedSlice { - #[inline(always)] - fn partial_cmp(&self, other: &Vec) -> Option { - self.as_slice().partial_cmp(other.as_slice()) - } -} - -unsafe impl SizedEncode for SizedSlice { - const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE * N; -} - -impl TryFrom<&[T]> for SizedSlice { - type Error = SizeError; - - #[inline(always)] - fn try_from(value: &[T]) -> Result { - Self::new(value) - } -} - -#[cfg(feature = "alloc")] -#[cfg_attr(doc, doc(cfg(feature = "alloc")))] -impl From> for Box<[T]> { - #[inline(always)] - fn from(value: SizedSlice) -> Self { - value.into_boxed_slice() - } -} - -#[cfg(feature = "alloc")] -#[cfg_attr(doc, doc(cfg(feature = "alloc")))] -impl From> for Vec { - #[inline(always)] - fn from(value: SizedSlice) -> Self { - value.into_vec() - } -} diff --git a/bzipper_macros/src/discriminant/mod.rs b/bzipper_macros/src/discriminant/mod.rs deleted file mode 100644 index a5d6a95..0000000 --- a/bzipper_macros/src/discriminant/mod.rs +++ /dev/null @@ -1,60 +0,0 @@ -// 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 . - -use proc_macro2::TokenStream; -use quote::ToTokens; -use syn::{Expr, Lit}; - -/// An enumeration discriminant. -#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] -#[repr(transparent)] -pub struct Discriminant(pub isize); - -impl Discriminant { - /// Parses the expression as a discriminant value. - /// - /// # Panics - /// - /// This constructor will panic if the provided expression is not a valid `isize` literal. - #[inline] - #[must_use] - pub fn parse(expr: &Expr) -> Self { - let Expr::Lit(ref expr) = *expr else { - panic!("expected literal expression for discriminant value"); - }; - - let Lit::Int(ref expr) = expr.lit else { - panic!("expected integer literal for discriminant value"); - }; - - let value = expr.base10_parse::() - .expect("expected `isize` literal for discriminant value"); - - Self(value) - } -} - -impl ToTokens for Discriminant { - #[inline(always)] - fn to_tokens(&self, tokens: &mut TokenStream) { - self.0.to_tokens(tokens); - } -} diff --git a/bzipper_macros/src/impls/decode_enum.rs b/bzipper_macros/src/impls/decode_enum.rs deleted file mode 100644 index c773de2..0000000 --- a/bzipper_macros/src/impls/decode_enum.rs +++ /dev/null @@ -1,67 +0,0 @@ -// 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 . - -use crate::DiscriminantIter; - -use proc_macro2::TokenStream; -use quote::quote; -use syn::{DataEnum, Fields, Token, Variant}; -use syn::punctuated::Punctuated; - -#[must_use] -pub fn decode_enum(data: &DataEnum) -> TokenStream { - let mut match_arms = Punctuated::::new(); - - for (discriminant, variant) in DiscriminantIter::new(&data.variants) { - let mut chain_commands = Punctuated::::new(); - - for field in &variant.fields { - let command = field.ident - .as_ref() - .map_or_else( - || quote! { ::bzipper::Decode::decode(stream)? }, - |field_name| quote! { #field_name: ::bzipper::Decode::decode(stream)? }, - ); - - chain_commands.push(command); - } - - let value = match *variant { - Variant { ident: ref variant_name, fields: Fields::Named( ..), .. } => quote! { Self::#variant_name { #chain_commands } }, - Variant { ident: ref variant_name, fields: Fields::Unnamed(..), .. } => quote! { Self::#variant_name(#chain_commands) }, - Variant { ident: ref variant_name, fields: Fields::Unit, .. } => quote! { Self::#variant_name }, - }; - - match_arms.push(quote! { #discriminant => #value }); - } - - match_arms.push(quote! { - value => return ::core::result::Result::Err(::bzipper::error::DecodeError::InvalidDiscriminant(value)) - }); - - quote! { - #[inline] - fn decode(stream: &mut ::bzipper::IStream) -> ::core::result::Result { - let value = match (::decode(stream)?) { #match_arms }; - Ok(value) - } - } -} diff --git a/bzipper_macros/src/impls/decode_struct.rs b/bzipper_macros/src/impls/decode_struct.rs deleted file mode 100644 index 9688e91..0000000 --- a/bzipper_macros/src/impls/decode_struct.rs +++ /dev/null @@ -1,55 +0,0 @@ -// 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 . - -use proc_macro2::TokenStream; -use quote::quote; -use syn::{DataStruct, Fields, Token}; -use syn::punctuated::Punctuated; - -#[must_use] -pub fn decode_struct(data: &DataStruct) -> TokenStream { - let mut chain_commands = Punctuated::::new(); - - for field in &data.fields { - let command = field.ident - .as_ref() - .map_or_else( - || quote! { ::bzipper::Decode::decode(stream)? }, - |field_name| quote! { #field_name: ::bzipper::Decode::decode(stream)? }, - ); - - chain_commands.push(command); - } - - let value = match data.fields { - Fields::Named( ..) => quote! { Self { #chain_commands } }, - Fields::Unnamed(..) => quote! { Self(#chain_commands) }, - Fields::Unit => quote! { Self }, - }; - - quote! { - #[inline] - fn decode(stream: &mut ::bzipper::IStream) -> ::core::result::Result { - let value = #value; - Ok(value) - } - } -} diff --git a/bzipper_macros/src/impls/encode_enum.rs b/bzipper_macros/src/impls/encode_enum.rs deleted file mode 100644 index 7ecf3d5..0000000 --- a/bzipper_macros/src/impls/encode_enum.rs +++ /dev/null @@ -1,77 +0,0 @@ -// 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 . - -use crate::DiscriminantIter; - -use proc_macro2::{Span, TokenStream}; -use quote::quote; -use syn::{ - DataEnum, - Fields, - Ident, - Variant, -}; - -#[must_use] -pub fn encode_enum(data: &DataEnum) -> TokenStream { - let mut match_arms = Vec::new(); - - // Iterate over each variant and give it a unique - // encoding scheme. - for (discriminant, variant) in DiscriminantIter::new(&data.variants) { - // The original identifiers of the fields: - let mut field_names = Vec::new(); - - // The captured field identifiers: - let mut field_captures = Vec::new(); - - for (index, field) in variant.fields.iter().enumerate() { - let capture = Ident::new(&format!("value{index}"), Span::call_site()); - - field_names.push(&field.ident); - field_captures.push(capture); - } - - let pattern = match *variant { - Variant { ident: ref variant_name, fields: Fields::Named( ..), .. } => quote! { Self::#variant_name { #(#field_names: ref #field_captures, )* } }, - Variant { ident: ref variant_name, fields: Fields::Unnamed(..), .. } => quote! { Self::#variant_name(#(ref #field_captures)*) }, - Variant { ident: ref variant_name, fields: Fields::Unit, .. } => quote! { Self::#variant_name }, - }; - - match_arms.push(quote! { - #pattern => { - ::bzipper::Encode::encode(&#discriminant, stream)?; - #(::bzipper::Encode::encode(#field_captures, stream)?;)* - } - }); - } - - quote! { - #[inline] - fn encode(&self, stream: &mut ::bzipper::OStream) -> ::core::result::Result<(), ::bzipper::error::EncodeError> { - match *self { - #(#match_arms)* - } - - Ok(()) - } - } -} diff --git a/bzipper_macros/src/impls/encode_struct.rs b/bzipper_macros/src/impls/encode_struct.rs deleted file mode 100644 index e853e44..0000000 --- a/bzipper_macros/src/impls/encode_struct.rs +++ /dev/null @@ -1,46 +0,0 @@ -// 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 . - -use proc_macro2::TokenStream; -use quote::{quote, ToTokens}; -use syn::{DataStruct, Index}; - -#[must_use] -pub fn encode_struct(data: &DataStruct) -> TokenStream { - let mut fields = Vec::new(); - - for (index, field) in data.fields.iter().enumerate() { - let name = field.ident - .as_ref() - .map_or_else(|| Index::from(index).to_token_stream(), ToTokens::to_token_stream); - - fields.push(name); - } - - quote! { - #[inline] - fn encode(&self, stream: &mut ::bzipper::OStream) -> ::core::result::Result<(), ::bzipper::error::EncodeError> { - #(::bzipper::Encode::encode(&self.#fields, stream)?;)* - - Ok(()) - } - } -} diff --git a/bzipper_macros/src/impls/sized_encode_enum.rs b/bzipper_macros/src/impls/sized_encode_enum.rs deleted file mode 100644 index 3bfc961..0000000 --- a/bzipper_macros/src/impls/sized_encode_enum.rs +++ /dev/null @@ -1,54 +0,0 @@ -// 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 . - -use proc_macro2::TokenStream; -use quote::quote; -use syn::DataEnum; - -#[must_use] -pub fn sized_encode_enum(data: &DataEnum) -> TokenStream { - let mut sizes = Vec::new(); - - // Iterate over each variant and give it a unique - // encoding scheme. - for variant in &data.variants { - let mut field_tys = Vec::new(); - - for field in &variant.fields { - field_tys.push(&field.ty); - } - - sizes.push(quote! { - ::MAX_ENCODED_SIZE - #(+ <#field_tys as ::bzipper::SizedEncode>::MAX_ENCODED_SIZE)* - }); - } - - quote! { - const MAX_ENCODED_SIZE: usize = const { - let mut max_encoded_size = 0x0usize; - - #(if #sizes > max_encoded_size { max_encoded_size = #sizes };)* - - max_encoded_size - }; - } -} diff --git a/bzipper_macros/src/lib.rs b/bzipper_macros/src/lib.rs deleted file mode 100644 index e92f8b0..0000000 --- a/bzipper_macros/src/lib.rs +++ /dev/null @@ -1,144 +0,0 @@ -// 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 . - -#![doc(html_logo_url = "https://gitlab.com/bjoernager/bzipper/-/raw/master/doc-icon.svg")] - -//! This crate implements procedural macros for [`bZipper`](https://crates.io/crates/bzipper/). - -use proc_macro::TokenStream; -use quote::quote; -use syn::{Data, DeriveInput}; - -macro_rules! use_mod { - ($vis:vis $name:ident) => { - mod $name; - $vis use $name::*; - }; -} -pub(crate) use use_mod; - -use_mod!(discriminant); -use_mod!(discriminant_iter); -use_mod!(generic_name); - -mod impls; - -#[proc_macro_derive(Decode)] -pub fn derive_decode(input: TokenStream) -> TokenStream { - let input = syn::parse_macro_input!(input as DeriveInput); - - let impl_body = match input.data { - Data::Enum( ref data) => impls::decode_enum( data), - Data::Struct(ref data) => impls::decode_struct(data), - - Data::Union(..) => panic!("unions cannot derive `Decode`"), - }; - - let ty_name = &input.ident; - - let generic_params = &input.generics.params; - let generic_where = &input.generics.where_clause; - - let generic_names = GenericName::extract_from(&input.generics); - - let output = quote! { - impl<#generic_params> ::bzipper::Decode for #ty_name<#generic_names> - #generic_where { - #impl_body - } - }; - - //panic!("{output}"); - - output.into() -} - -#[proc_macro_derive(Encode)] -pub fn derive_encode(input: TokenStream) -> TokenStream { - let input = syn::parse_macro_input!(input as DeriveInput); - - let impl_body = match input.data { - Data::Enum( ref data) => impls::encode_enum( data), - Data::Struct(ref data) => impls::encode_struct(data), - - Data::Union(..) => panic!("unions cannot derive `Encode`"), - }; - - let ty_name = &input.ident; - - let generic_params = &input.generics.params; - let generic_where = &input.generics.where_clause; - - let generic_names = GenericName::extract_from(&input.generics); - - let output = quote! { - impl<#generic_params> ::bzipper::Encode for #ty_name<#generic_names> - #generic_where { - #impl_body - } - }; - - //panic!("{output}"); - - output.into() -} - -#[proc_macro_derive(SizedEncode)] -pub fn derive_sized_encode(input: TokenStream) -> TokenStream { - let input = syn::parse_macro_input!(input as DeriveInput); - - let encode_impl_body = match input.data { - Data::Enum( ref data) => impls::encode_enum( data), - Data::Struct(ref data) => impls::encode_struct(data), - - Data::Union(..) => panic!("unions can neither derive `Encode` nor `SizedEncode`"), - }; - - let sized_encode_impl_body = match input.data { - Data::Enum( ref data) => impls::sized_encode_enum( data), - Data::Struct(ref data) => impls::sized_encode_struct(data), - - Data::Union(..) => unreachable!(), - }; - - let ty_name = &input.ident; - - let generic_params = &input.generics.params; - let generic_where = &input.generics.where_clause; - - let generic_names = GenericName::extract_from(&input.generics); - - let output = quote! { - impl<#generic_params> ::bzipper::Encode for #ty_name<#generic_names> - #generic_where { - #encode_impl_body - } - - unsafe impl<#generic_params> ::bzipper::SizedEncode for #ty_name<#generic_names> - #generic_where { - #sized_encode_impl_body - } - }; - - //panic!("{output}"); - - output.into() -} diff --git a/bzipper_benchmarks/Cargo.toml b/librum-benchmarks/Cargo.toml similarity index 63% rename from bzipper_benchmarks/Cargo.toml rename to librum-benchmarks/Cargo.toml index 4b1fe83..d14fb86 100644 --- a/bzipper_benchmarks/Cargo.toml +++ b/librum-benchmarks/Cargo.toml @@ -1,8 +1,8 @@ [package] -name = "bzipper_benchmarks" -version = "0.11.0" +name = "librum-benchmarks" +version = "0.12.0" edition = "2021" -description = "bZipper benchmarks." +description = "Librum benchmarks." authors.workspace = true readme.workspace = true @@ -10,11 +10,10 @@ homepage.workspace = true repository.workspace = true [dependencies] -bzipper = { path = "../bzipper", version = "0.11.0" } +librum = { path = "../librum", version = "0.12.0", features = ["proc-macro"]} -bincode = "1.3.3" -ciborium = "0.2.2" -rand = "0.8.5" +bincode = "1.3.3" +rand = "0.8.5" borsh = { version = "1.5.1", features = ["derive"] } postcard = { version = "1.0.10", features = ["use-std"] } diff --git a/bzipper_benchmarks/src/main.rs b/librum-benchmarks/src/main.rs similarity index 54% rename from bzipper_benchmarks/src/main.rs rename to librum-benchmarks/src/main.rs index 7a8d7a4..f080668 100644 --- a/bzipper_benchmarks/src/main.rs +++ b/librum-benchmarks/src/main.rs @@ -15,9 +15,7 @@ macro_rules! benchmark { borsh: $borsh_op:block$(,)? - bzipper: $bzipper_op:block$(,)? - - ciborium: $ciborium_op:block$(,)? + librum: $librum_op:block$(,)? postcard: $postcard_op:block$(,)? }$(,)?)+ @@ -53,8 +51,7 @@ macro_rules! benchmark { let mut total_bincode_duration = 0.0; let mut total_borsh_duration = 0.0; - let mut total_bzipper_duration = 0.0; - let mut total_ciborium_duration = 0.0; + let mut total_librum_duration = 0.0; let mut total_postcard_duration = 0.0; $({ @@ -64,41 +61,37 @@ macro_rules! benchmark { let bincode_duration = time! { $bincode_op }; let borsh_duration = time! { $borsh_op }; - let bzipper_duration = time! { $bzipper_op }; - let ciborium_duration = time! { $ciborium_op }; + let librum_duration = time! { $librum_op }; let postcard_duration = time! { $postcard_op }; eprint!("\u{001B}[000m"); - eprintln!("bincode: {}", format_score(bincode_duration, bzipper_duration)); - eprintln!("borsh: {}", format_score(borsh_duration, bzipper_duration)); - eprintln!("bzipper: {}", format_score(bzipper_duration, bzipper_duration)); - eprintln!("ciborium: {}", format_score(ciborium_duration, bzipper_duration)); - eprintln!("postcard: {}", format_score(postcard_duration, bzipper_duration)); + eprintln!("bincode: {}", format_score(bincode_duration, librum_duration)); + eprintln!("borsh: {}", format_score(borsh_duration, librum_duration)); + eprintln!("librum: {}", format_score(librum_duration, librum_duration)); + eprintln!("postcard: {}", format_score(postcard_duration, librum_duration)); total_bincode_duration += bincode_duration; total_borsh_duration += borsh_duration; - total_bzipper_duration += bzipper_duration; - total_ciborium_duration += ciborium_duration; + total_librum_duration += librum_duration; total_postcard_duration += postcard_duration; })* eprintln!(); eprintln!("\u{001B}[001mtotal score:\u{001B}[022m"); - eprintln!("bincode: {}", format_score(total_bincode_duration, total_bzipper_duration)); - eprintln!("borsh: {}", format_score(total_borsh_duration, total_bzipper_duration)); - eprintln!("bzipper: {}", format_score(total_bzipper_duration, total_bzipper_duration)); - eprintln!("ciborium: {}", format_score(total_ciborium_duration, total_bzipper_duration)); - eprintln!("postcard: {}", format_score(total_postcard_duration, total_bzipper_duration)); + eprintln!("bincode: {}", format_score(total_bincode_duration, total_librum_duration)); + eprintln!("borsh: {}", format_score(total_borsh_duration, total_librum_duration)); + eprintln!("librum: {}", format_score(total_librum_duration, total_librum_duration)); + eprintln!("postcard: {}", format_score(total_postcard_duration, total_librum_duration)); }}; } -#[derive(bzipper::Decode, bzipper::SizedEncode)] +#[derive(librum::Decode, librum::Encode, librum::SizedEncode)] #[derive(borsh::BorshSerialize)] #[derive(serde::Deserialize, serde::Serialize)] #[repr(transparent)] struct Unit; -#[derive(bzipper::Decode, bzipper::SizedEncode)] +#[derive(librum::Decode, librum::Encode, librum::SizedEncode)] #[derive(borsh::BorshSerialize)] #[derive(serde::Deserialize, serde::Serialize)] #[repr(transparent)] @@ -112,13 +105,13 @@ impl Unnamed { } } -#[derive(bzipper::Decode, bzipper::SizedEncode)] +#[derive(librum::Decode, librum::Encode, librum::SizedEncode)] #[derive(borsh::BorshSerialize)] #[derive(serde::Deserialize, serde::Serialize)] #[repr(transparent)] struct Named { buf: [u8; 0x8] } -#[derive(bzipper::Decode, bzipper::SizedEncode)] +#[derive(librum::Decode, librum::Encode, librum::SizedEncode)] #[derive(borsh::BorshSerialize)] #[derive(serde::Deserialize, serde::Serialize)] enum Enum { @@ -147,16 +140,15 @@ impl Named { } fn main() { - println!("######################"); - println!("# BZIPPER BENCHMARKS #"); - println!("######################"); + println!("#####################"); + println!("# LIBRUM BENCHMARKS #"); + println!("#####################"); println!(); println!("Each benchmark has a version written for the following crates:"); println!(); println!("- Bincode: "); println!("- Borsh: "); - println!("- Ciborium: "); - println!("- bzipper: "); + println!("- Librum: "); println!("- Postcard: "); println!(); println!("The total time the benchmark took (including memory allocations and dealloca-"); @@ -166,10 +158,10 @@ fn main() { println!(); println!("When every benchmark has concluded, the total run time and vps is listed for"); println!("each crate. A percantage additionally compares the run time between the listed"); - println!("crate and bzipper (which should always be `0%` for bzipper itself). DO NOTE THAT"); - println!("THESE FINAL RESULTS INDICATE A NON-WEIGHTED AVERAGE ACROSS BENCHMARKS. It can"); - println!("therefore be skewed relative to real-world performance by the similarity of some"); - println!("benchmarks."); + println!("crate and librum (which should always be c. `0%` for Librum itself). DO NOTE"); + println!("THAT THESE FINAL RESULTS INDICATE A NON-WEIGHTED AVERAGE ACROSS BENCHMARKS. It"); + println!("can therefore be skewed relative to real-world performance by the similarity of"); + println!("some benchmarks."); println!(); eprintln!("value_count: {VALUE_COUNT}"); @@ -180,11 +172,10 @@ fn main() { // Requires `std`. use bincode::serialize_into; - use std::io::Cursor; - let buf_size = size_of::(); // value + let buf_size = size_of::(); - let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT]); + let mut buf = vec![0x00; buf_size * VALUE_COUNT]; for _ in 0x0..VALUE_COUNT { serialize_into(&mut buf, &random::()).unwrap(); @@ -192,21 +183,19 @@ fn main() { } borsh: { - use std::io::Cursor; + let buf_size = size_of::(); - let buf_size = size_of::(); // value - - let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT]); + let mut buf = vec![0x00; buf_size * VALUE_COUNT]; for _ in 0x0..VALUE_COUNT { borsh::to_writer(&mut buf, &random::()).unwrap(); } } - bzipper: { - use bzipper::{Encode, OStream, SizedEncode}; + librum: { + use librum::{Encode, OStream, SizedEncode}; - let buf_size = u8::MAX_ENCODED_SIZE; // value + let buf_size = u8::MAX_ENCODED_SIZE; let mut buf = vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice(); let mut stream = OStream::new(&mut buf); @@ -216,26 +205,10 @@ fn main() { } } - ciborium: { - use std::io::Cursor; - - let buf_size = - size_of::() // header - + size_of::(); // value - - let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT]); - - for _ in 0x0..VALUE_COUNT { - ciborium::into_writer(&random::(), &mut buf).unwrap(); - } - } - postcard: { - use std::io::Cursor; + let buf_size = size_of::(); - let buf_size = size_of::(); // value - - let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT]); + let mut buf = vec![0x00; buf_size * VALUE_COUNT]; for _ in 0x0..VALUE_COUNT { postcard::to_io(&random::(), &mut buf).unwrap(); @@ -243,14 +216,107 @@ fn main() { } } + encode_u32: { + bincode: { + use bincode::serialize_into; + + let buf_size = size_of::(); + + let mut buf = vec![0x00; buf_size * VALUE_COUNT]; + + for _ in 0x0..VALUE_COUNT { + serialize_into(&mut buf, &random::()).unwrap(); + } + } + + borsh: { + let buf_size = size_of::(); + + let mut buf = vec![0x00; buf_size * VALUE_COUNT]; + + for _ in 0x0..VALUE_COUNT { + borsh::to_writer(&mut buf, &random::()).unwrap(); + } + } + + librum: { + use librum::{Encode, OStream, SizedEncode}; + + let buf_size = u32::MAX_ENCODED_SIZE; + + let mut buf = vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice(); + let mut stream = OStream::new(&mut buf); + + for _ in 0x0..VALUE_COUNT { + random::().encode(&mut stream).unwrap(); + } + } + + postcard: { + let buf_size = size_of::(); + + let mut buf = vec![0x00; buf_size * VALUE_COUNT]; + + for _ in 0x0..VALUE_COUNT { + postcard::to_io(&random::(), &mut buf).unwrap(); + } + } + } + + encode_u128: { + bincode: { + use bincode::serialize_into; + + let buf_size = size_of::(); + + let mut buf = vec![0x00; buf_size * VALUE_COUNT]; + + for _ in 0x0..VALUE_COUNT { + serialize_into(&mut buf, &random::()).unwrap(); + } + } + + borsh: { + let buf_size = size_of::(); + + let mut buf = vec![0x00; buf_size * VALUE_COUNT]; + + for _ in 0x0..VALUE_COUNT { + borsh::to_writer(&mut buf, &random::()).unwrap(); + } + } + + librum: { + use librum::{Encode, OStream, SizedEncode}; + + let buf_size = u128::MAX_ENCODED_SIZE; + + let mut buf = vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice(); + let mut stream = OStream::new(&mut buf); + + for _ in 0x0..VALUE_COUNT { + random::().encode(&mut stream).unwrap(); + } + } + + postcard: { + let buf_size = size_of::(); + + let mut buf = vec![0x00; buf_size * VALUE_COUNT]; + + for _ in 0x0..VALUE_COUNT { + postcard::to_io(&random::(), &mut buf).unwrap(); + } + } + } + encode_struct_unit: { bincode: { use bincode::serialize_into; - use std::io::Cursor; - let buf_size = size_of::(); // value + let buf_size = size_of::(); - let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT]); + let mut buf = vec![0x00; buf_size * VALUE_COUNT]; for _ in 0x0..VALUE_COUNT { serialize_into(&mut buf, &Unit).unwrap(); @@ -258,21 +324,19 @@ fn main() { } borsh: { - use std::io::Cursor; + let buf_size = size_of::(); - let buf_size = size_of::(); // value - - let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice()); + let mut buf = vec![0x00; buf_size * VALUE_COUNT]; for _ in 0x0..VALUE_COUNT { borsh::to_writer(&mut buf, &Unit).unwrap(); } } - bzipper: { - use bzipper::{Encode, OStream, SizedEncode}; + librum: { + use librum::{Encode, OStream, SizedEncode}; - let buf_size = Unit::MAX_ENCODED_SIZE; // value + let buf_size = Unit::MAX_ENCODED_SIZE; let mut buf = vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice(); let mut stream = OStream::new(&mut buf); @@ -282,26 +346,10 @@ fn main() { } } - ciborium: { - use std::io::Cursor; - - let buf_size = - size_of::() // header - + size_of::(); // value - - let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice()); - - for _ in 0x0..VALUE_COUNT { - ciborium::into_writer(&Unit, &mut buf).unwrap(); - } - } - postcard: { - use std::io::Cursor; + let buf_size = size_of::(); - let buf_size = size_of::(); // value - - let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice()); + let mut buf = vec![0x00; buf_size * VALUE_COUNT]; for _ in 0x0..VALUE_COUNT { postcard::to_io(&Unit, &mut buf).unwrap(); @@ -312,11 +360,10 @@ fn main() { encode_struct_unnamed: { bincode: { use bincode::serialize_into; - use std::io::Cursor; - let buf_size = size_of::(); // value + let buf_size = size_of::(); - let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT]); + let mut buf = vec![0x00; buf_size * VALUE_COUNT]; for _ in 0x0..VALUE_COUNT { serialize_into(&mut buf, &Unnamed::from_char(random())).unwrap(); @@ -324,21 +371,19 @@ fn main() { } borsh: { - use std::io::Cursor; + let buf_size = size_of::(); - let buf_size = size_of::(); // value - - let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice()); + let mut buf = vec![0x00; buf_size * VALUE_COUNT]; for _ in 0x0..VALUE_COUNT { borsh::to_writer(&mut buf, &Unnamed::from_char(random())).unwrap(); } } - bzipper: { - use bzipper::{Encode, OStream, SizedEncode}; + librum: { + use librum::{Encode, OStream, SizedEncode}; - let buf_size = Unnamed::MAX_ENCODED_SIZE; // value + let buf_size = Unnamed::MAX_ENCODED_SIZE; let mut buf = vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice(); let mut stream = OStream::new(&mut buf); @@ -348,26 +393,10 @@ fn main() { } } - ciborium: { - use std::io::Cursor; - - let buf_size = - size_of::() // header - + size_of::(); // value - - let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice()); - - for _ in 0x0..VALUE_COUNT { - ciborium::into_writer(&Unnamed::from_char(random()), &mut buf).unwrap(); - } - } - postcard: { - use std::io::Cursor; + let buf_size = size_of::(); - let buf_size = size_of::(); // value - - let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice()); + let mut buf = vec![0x00; buf_size * VALUE_COUNT]; for _ in 0x0..VALUE_COUNT { postcard::to_io(&Unnamed::from_char(random()), &mut buf).unwrap(); @@ -378,11 +407,10 @@ fn main() { encode_struct_named: { bincode: { use bincode::serialize_into; - use std::io::Cursor; - let buf_size = size_of::(); // value + let buf_size = size_of::(); - let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT]); + let mut buf = vec![0x00; buf_size * VALUE_COUNT]; for _ in 0x0..VALUE_COUNT { serialize_into(&mut buf, &Named::from_u64(random())).unwrap(); @@ -390,21 +418,19 @@ fn main() { } borsh: { - use std::io::Cursor; + let buf_size = size_of::(); - let buf_size = size_of::(); // value - - let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice()); + let mut buf = vec![0x00; buf_size * VALUE_COUNT]; for _ in 0x0..VALUE_COUNT { borsh::to_writer(&mut buf, &Named::from_u64(random())).unwrap(); } } - bzipper: { - use bzipper::{Encode, OStream, SizedEncode}; + librum: { + use librum::{Encode, OStream, SizedEncode}; - let buf_size = Named::MAX_ENCODED_SIZE; // value + let buf_size = Named::MAX_ENCODED_SIZE; let mut buf = vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice(); let mut stream = OStream::new(&mut buf); @@ -414,28 +440,10 @@ fn main() { } } - ciborium: { - use std::io::Cursor; - - let buf_size = - size_of::() // header - + size_of::() // tag - + size_of::() // header - + size_of::(); // value - - let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice()); - - for _ in 0x0..VALUE_COUNT { - ciborium::into_writer(&Named::from_u64(random()), &mut buf).unwrap(); - } - } - postcard: { - use std::io::Cursor; + let buf_size = size_of::(); - let buf_size = size_of::(); // value - - let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice()); + let mut buf = vec![0x00; buf_size * VALUE_COUNT]; for _ in 0x0..VALUE_COUNT { postcard::to_io(&Named::from_u64(random()), &mut buf).unwrap(); @@ -446,13 +454,12 @@ fn main() { encode_enum_unit: { bincode: { use bincode::serialize_into; - use std::io::Cursor; let buf_size = - size_of::() // discriminant - + size_of::(); // value + size_of::() // discriminant + + size_of::(); - let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT]); + let mut buf = vec![0x00; buf_size * VALUE_COUNT]; for _ in 0x0..VALUE_COUNT { serialize_into(&mut buf, &Enum::Unit(Unit)).unwrap(); @@ -460,25 +467,23 @@ fn main() { } borsh: { - use std::io::Cursor; - let buf_size = - size_of::() // discriminant - + size_of::(); // value + size_of::() // discriminant + + size_of::(); - let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice()); + let mut buf = vec![0x00; buf_size * VALUE_COUNT]; for _ in 0x0..VALUE_COUNT { borsh::to_writer(&mut buf, &Enum::Unit(Unit)).unwrap(); } } - bzipper: { - use bzipper::{Encode, OStream, SizedEncode}; + librum: { + use librum::{Encode, OStream, SizedEncode}; let buf_size = - isize::MAX_ENCODED_SIZE // discriminant - + Unit::MAX_ENCODED_SIZE; // value + isize::MAX_ENCODED_SIZE // discriminant + + Unit::MAX_ENCODED_SIZE; let mut buf = vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice(); let mut stream = OStream::new(&mut buf); @@ -488,30 +493,12 @@ fn main() { } } - ciborium: { - use std::io::Cursor; - - let buf_size = - size_of::() // header - + size_of::() // tag (discriminant) - + size_of::() // header - + size_of::(); // value - - let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice()); - - for _ in 0x0..VALUE_COUNT { - ciborium::into_writer(&Enum::Unit(Unit), &mut buf).unwrap(); - } - } - postcard: { - use std::io::Cursor; - let buf_size = - size_of::() // discriminant - + size_of::(); // value + size_of::() // discriminant + + size_of::(); - let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT].into_boxed_slice()); + let mut buf = vec![0x00; buf_size * VALUE_COUNT]; for _ in 0x0..VALUE_COUNT { postcard::to_io(&Enum::Unit(Unit), &mut buf).unwrap(); diff --git a/bzipper_macros/Cargo.toml b/librum-macros/Cargo.toml similarity index 78% rename from bzipper_macros/Cargo.toml rename to librum-macros/Cargo.toml index 1904763..cef6b99 100644 --- a/bzipper_macros/Cargo.toml +++ b/librum-macros/Cargo.toml @@ -1,8 +1,8 @@ [package] -name = "bzipper_macros" -version = "0.11.0" +name = "librum-macros" +version = "0.12.0" edition = "2021" -documentation = "https://docs.rs/bzipper_macros/" +documentation = "https://docs.rs/librum-macros/" authors.workspace = true description.workspace = true diff --git a/bzipper_macros/src/discriminant_iter/mod.rs b/librum-macros/src/discriminants/mod.rs similarity index 50% rename from bzipper_macros/src/discriminant_iter/mod.rs rename to librum-macros/src/discriminants/mod.rs index ea34f6d..b3883f4 100644 --- a/bzipper_macros/src/discriminant_iter/mod.rs +++ b/librum-macros/src/discriminants/mod.rs @@ -1,35 +1,34 @@ // Copyright 2024 Gabriel Bjørnager Jensen. // -// This file is part of bZipper. +// This file is part of Librum. // -// bZipper is free software: you can redistribute -// it and/or modify it under the terms of the GNU +// Librum 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 +// Librum 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 +// er General Public License along with Librum. If // not, see . -use crate::Discriminant; - use std::borrow::Borrow; -use syn::Variant; +use proc_macro2::Span; +use syn::{Expr, Lit, LitInt, Variant}; -pub struct DiscriminantIter>> { +pub struct Discriminants>> { variants: I::IntoIter, - prev: Option, + prev: Option, } -impl>> DiscriminantIter { +impl>> Discriminants { #[inline(always)] #[must_use] pub fn new(variants: I) -> Self { @@ -40,28 +39,43 @@ impl>> DiscriminantIter { } } -impl>> Iterator for DiscriminantIter { - type Item = (Discriminant, I::Item); +impl>> Iterator for Discriminants { + type Item = LitInt; #[inline] fn next(&mut self) -> Option { let variant = self.variants.next()?; - let discriminant = if let Some((_, ref discriminant)) = variant.borrow().discriminant { - Discriminant::parse(discriminant) - } else if let Some(discriminant) = self.prev { - let value = discriminant.0 - .checked_add(0x1) - .unwrap_or_else(|| panic!("overflow following discriminant `{discriminant:?}`")); + let discriminant = if let Some((_, ref expr)) = variant.borrow().discriminant { + let Expr::Lit(ref expr) = *expr else { + panic!("expected literal expression for discriminant value"); + }; - Discriminant(value) + let Lit::Int(ref expr) = expr.lit else { + panic!("expected (potentially signed) integer literal for discriminant value`"); + }; + + let expr = expr.base10_digits(); + + let value: u128 = expr + .parse() + .or_else(|_| expr.parse::().map(|v| v as u128)) + .unwrap(); + + value + } else if let Some(prev) = self.prev { + prev + .checked_add(0x1) + .unwrap_or_else(|| panic!("overflow following discriminant `{prev:?}`")) } else { Default::default() }; self.prev = Some(discriminant); - Some((discriminant, variant)) + let discriminant = LitInt::new(&discriminant.to_string(), Span::call_site()); + + Some(discriminant) } #[inline(always)] diff --git a/bzipper_macros/src/generic_name/mod.rs b/librum-macros/src/generic_name/mod.rs similarity index 89% rename from bzipper_macros/src/generic_name/mod.rs rename to librum-macros/src/generic_name/mod.rs index 6fc0bc9..d9a372a 100644 --- a/bzipper_macros/src/generic_name/mod.rs +++ b/librum-macros/src/generic_name/mod.rs @@ -1,22 +1,22 @@ // Copyright 2024 Gabriel Bjørnager Jensen. // -// This file is part of bZipper. +// This file is part of Librum. // -// bZipper is free software: you can redistribute -// it and/or modify it under the terms of the GNU +// Librum 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 +// Librum 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 +// er General Public License along with Librum. If // not, see . use proc_macro2::TokenStream; diff --git a/librum-macros/src/impl_derive_macro.rs b/librum-macros/src/impl_derive_macro.rs new file mode 100644 index 0000000..3c6f025 --- /dev/null +++ b/librum-macros/src/impl_derive_macro.rs @@ -0,0 +1,80 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Librum. +// +// Librum 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. +// +// Librum 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 Librum. If +// not, see . + +use crate::{GenericName, Repr}; + +use proc_macro2::TokenStream; +use quote::quote; +use syn::{ + Data, + DataEnum, + DataStruct, + DeriveInput, + Path, + Token, +}; + +pub fn impl_derive_macro( + input: DeriveInput, + trait_path: Path, + r#unsafe_token: Option, + struct_body: S, + enum_body: E, +) -> TokenStream +where + S: FnOnce(DataStruct) -> TokenStream, + E: FnOnce(DataEnum, Repr) -> TokenStream, +{ + let trait_name = &trait_path + .segments + .last() + .expect("expected non-empty path for derived trait") + .ident; + + let self_name = &input.ident; + + let body = match input.data { + Data::Struct(data) => struct_body(data), + + Data::Enum(data) => { + let repr = Repr::get(&input.attrs).unwrap_or_default(); + + enum_body(data, repr) + } + + Data::Union(..) => panic!("unions cannot derive `{trait_name:?}`"), + }; + + let generic_params = &input.generics.params; + let generic_where = &input.generics.where_clause; + + let generic_names = GenericName::extract_from(&input.generics); + + let output = quote! { + #unsafe_token impl<#generic_params> #trait_path for #self_name<#generic_names> + #generic_where + { + #body + } + }; + + output +} \ No newline at end of file diff --git a/librum-macros/src/impls/decode_enum.rs b/librum-macros/src/impls/decode_enum.rs new file mode 100644 index 0000000..3da3a38 --- /dev/null +++ b/librum-macros/src/impls/decode_enum.rs @@ -0,0 +1,82 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Librum. +// +// Librum 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. +// +// Librum 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 Librum. If +// not, see . + +use crate::{Discriminants, Repr}; + +use proc_macro2::TokenStream; +use quote::quote; +use std::iter; +use syn::{DataEnum, Fields}; + +#[must_use] +pub fn decode_enum(data: DataEnum, repr: Repr) -> TokenStream { + let discriminants: Vec<_> = Discriminants::new(&data.variants).collect(); + + let values = data + .variants + .into_iter() + .map(|variant| { + let variant_name = variant.ident; + + let commands = iter::repeat_n( + quote! { + ::librum::Decode::decode(stream) + .map_err(::core::convert::Into::<::librum::error::GenericDecodeError>::into) + .map_err(::librum::error::EnumDecodeError::Field)? + }, + variant.fields.len(), + ); + + match variant.fields { + Fields::Unit => quote! { Self::#variant_name }, + + Fields::Unnamed(_fields) => quote! { Self::#variant_name (#(#commands, )*) }, + + Fields::Named(fields) => { + let field_names = fields + .named + .into_iter() + .map(|field| field.ident.unwrap()); + + quote! { Self::#variant_name { #(#field_names: #commands, )* } } + }, + } + }); + + quote! { + type Error = ::librum::error::EnumDecodeError<#repr, ::librum::error::GenericDecodeError>; + + #[inline] + fn decode(stream: &mut ::librum::IStream) -> ::core::result::Result { + let discriminant = <#repr as ::librum::Decode>::decode(stream) + .map_err(::core::convert::Into::<::core::convert::Infallible>::into) + .map_err(::librum::error::EnumDecodeError::InvalidDiscriminant)?; + + let this = match discriminant { + #(#discriminants => #values,)* + + value => return ::core::result::Result::Err(::librum::error::EnumDecodeError::UnassignedDiscriminant { value }), + }; + + ::core::result::Result::Ok(this) + } + } +} diff --git a/librum-macros/src/impls/decode_struct.rs b/librum-macros/src/impls/decode_struct.rs new file mode 100644 index 0000000..0270c7c --- /dev/null +++ b/librum-macros/src/impls/decode_struct.rs @@ -0,0 +1,61 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Librum. +// +// Librum 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. +// +// Librum 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 Librum. If +// not, see . + +use proc_macro2::TokenStream; +use quote::quote; +use syn::{DataStruct, Fields}; +use std::iter; + +#[must_use] +pub fn decode_struct(data: DataStruct) -> TokenStream { + let commands = iter::repeat_n( + quote! { + ::librum::Decode::decode(stream) + .map_err(::core::convert::Into::<::librum::error::GenericDecodeError>::into)? + }, + data.fields.len(), + ); + + let value = match data.fields { + Fields::Unit => quote! { Self }, + + Fields::Unnamed(_fields) => quote! { Self (#(#commands, )*) }, + + Fields::Named(fields) => { + let field_names = fields + .named + .into_iter() + .map(|field| field.ident.unwrap()); + + quote! { Self { #(#field_names: #commands, )* } } + }, + }; + + quote! { + type Error = ::librum::error::GenericDecodeError; + + #[inline] + fn decode(stream: &mut ::librum::IStream) -> ::core::result::Result { + let this = #value; + ::core::result::Result::Ok(this) + } + } +} diff --git a/librum-macros/src/impls/encode_enum.rs b/librum-macros/src/impls/encode_enum.rs new file mode 100644 index 0000000..19c949d --- /dev/null +++ b/librum-macros/src/impls/encode_enum.rs @@ -0,0 +1,94 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Librum. +// +// Librum 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. +// +// Librum 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 Librum. If +// not, see . + +use crate::{Discriminants, Repr}; + +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use syn::{DataEnum, Fields, Ident, LitInt}; + +#[must_use] +pub fn encode_enum(data: DataEnum, repr: Repr) -> TokenStream { + let discriminants: Vec = Discriminants::new(&data.variants).collect(); + + let captures: Vec> = data + .variants + .iter() + .map(|variant| { + variant + .fields + .iter() + .enumerate() + .map(|(index, _)| Ident::new(&format!("value{index}"), Span::call_site())) + .collect() + }) + .collect(); + + let patterns = data + .variants + .into_iter() + .zip(&captures) + .map(|(variant, captures)| { + let variant_name = variant.ident; + + match variant.fields { + Fields::Unit => quote! { Self::#variant_name }, + + Fields::Unnamed(_fields) => quote! { Self::#variant_name (#(ref #captures, )*) }, + + Fields::Named(fields) => { + let field_names = fields + .named + .into_iter() + .map(|field| field.ident.unwrap()); + + quote! { Self::#variant_name { #(#field_names: ref #captures, )* } } + }, + } + }); + + quote! { + type Error = ::librum::error::EnumEncodeError<#repr, ::librum::error::GenericEncodeError>; + + #[allow(unreachable_patterns)] + #[inline] + fn encode(&self, stream: &mut ::librum::OStream) -> ::core::result::Result<(), Self::Error> { + match *self { + #( + #patterns => { + <#repr as ::librum::Encode>::encode(&#discriminants, stream) + .map_err(::librum::error::EnumEncodeError::Discriminant)?; + + #( + ::librum::Encode::encode(#captures, stream) + .map_err(::core::convert::Into::<::librum::error::GenericEncodeError>::into) + .map_err(::librum::error::EnumEncodeError::Field)?; + )* + } + )* + + _ => ::core::unreachable!("no variants defined for this enumeration"), + } + + ::core::result::Result::Ok(()) + } + } +} diff --git a/librum-macros/src/impls/encode_struct.rs b/librum-macros/src/impls/encode_struct.rs new file mode 100644 index 0000000..da37fbc --- /dev/null +++ b/librum-macros/src/impls/encode_struct.rs @@ -0,0 +1,65 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Librum. +// +// Librum 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. +// +// Librum 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 Librum. If +// not, see . + +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use syn::{DataStruct, Fields, Ident}; + +#[must_use] +pub fn encode_struct(data: DataStruct) -> TokenStream { + let captures: Vec<_> = data + .fields + .iter() + .enumerate() + .map(|(index, _)| Ident::new(&format!("value{index}"), Span::call_site())) + .collect(); + + let pattern = match data.fields { + Fields::Unit => quote! { Self }, + + Fields::Unnamed(_fields) => quote! { Self(#(ref #captures, )*) }, + + Fields::Named(fields) => { + let field_names = fields + .named + .into_iter() + .map(|field| field.ident.unwrap()); + + quote! { Self { #(#field_names: ref #captures, )* } } + }, + }; + + quote! { + type Error = ::librum::error::GenericEncodeError; + + #[inline] + fn encode(&self, stream: &mut ::librum::OStream) -> ::core::result::Result<(), Self::Error> { + let #pattern = self; + + #( + ::librum::Encode::encode(#captures, stream) + .map_err(::core::convert::Into::<::librum::error::GenericEncodeError>::into)?; + )* + + ::core::result::Result::Ok(()) + } + } +} diff --git a/bzipper_macros/src/impls/mod.rs b/librum-macros/src/impls/mod.rs similarity index 75% rename from bzipper_macros/src/impls/mod.rs rename to librum-macros/src/impls/mod.rs index cdbc9ac..c272ca6 100644 --- a/bzipper_macros/src/impls/mod.rs +++ b/librum-macros/src/impls/mod.rs @@ -1,22 +1,22 @@ // Copyright 2024 Gabriel Bjørnager Jensen. // -// This file is part of bZipper. +// This file is part of Librum. // -// bZipper is free software: you can redistribute -// it and/or modify it under the terms of the GNU +// Librum 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 +// Librum 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 +// er General Public License along with Librum. If // not, see . use crate::use_mod; diff --git a/librum-macros/src/impls/sized_encode_enum.rs b/librum-macros/src/impls/sized_encode_enum.rs new file mode 100644 index 0000000..23ddd15 --- /dev/null +++ b/librum-macros/src/impls/sized_encode_enum.rs @@ -0,0 +1,59 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Librum. +// +// Librum 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. +// +// Librum 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 Librum. If +// not, see . + +use crate::Repr; + +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use std::iter; +use syn::DataEnum; + +#[must_use] +pub fn sized_encode_enum(data: DataEnum, repr: Repr) -> TokenStream { + let tys: Vec> = data + .variants + .iter() + .map(|variant| { + variant + .fields + .iter() + .map(|field| field.ty.clone()) + .chain(iter::once(repr.to_type(Span::call_site()))) + .collect() + }) + .collect(); + + quote! { + const MAX_ENCODED_SIZE: usize = { + let mut total_size = 0x0usize; + + let mut current_size = 0x0usize; + + #( + current_size = 0x0 #(+ <#tys as ::librum::SizedEncode>::MAX_ENCODED_SIZE)*; + + if current_size > total_size { total_size = current_size }; + )* + + total_size + }; + } +} diff --git a/bzipper_macros/src/impls/sized_encode_struct.rs b/librum-macros/src/impls/sized_encode_struct.rs similarity index 55% rename from bzipper_macros/src/impls/sized_encode_struct.rs rename to librum-macros/src/impls/sized_encode_struct.rs index e194e08..95ddd52 100644 --- a/bzipper_macros/src/impls/sized_encode_struct.rs +++ b/librum-macros/src/impls/sized_encode_struct.rs @@ -1,22 +1,22 @@ // Copyright 2024 Gabriel Bjørnager Jensen. // -// This file is part of bZipper. +// This file is part of Librum. // -// bZipper is free software: you can redistribute -// it and/or modify it under the terms of the GNU +// Librum 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 +// Librum 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 +// er General Public License along with Librum. If // not, see . use proc_macro2::TokenStream; @@ -24,14 +24,13 @@ use quote::quote; use syn::DataStruct; #[must_use] -pub fn sized_encode_struct(data: &DataStruct) -> TokenStream { - let mut field_tys = Vec::new(); - - for field in &data.fields { - field_tys.push(&field.ty); - } +pub fn sized_encode_struct(data: DataStruct) -> TokenStream { + let tys: Vec<_> = data.fields + .into_iter() + .map(|field| field.ty) + .collect(); quote! { - const MAX_ENCODED_SIZE: usize = 0x0 #( + <#field_tys as ::bzipper::SizedEncode>::MAX_ENCODED_SIZE)*; + const MAX_ENCODED_SIZE: usize = 0x0 #( + <#tys as ::librum::SizedEncode>::MAX_ENCODED_SIZE)*; } } diff --git a/librum-macros/src/lib.rs b/librum-macros/src/lib.rs new file mode 100644 index 0000000..2097b1c --- /dev/null +++ b/librum-macros/src/lib.rs @@ -0,0 +1,97 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Librum. +// +// Librum 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. +// +// Librum 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 Librum. If +// not, see . + +#![doc(html_logo_url = "https://gitlab.com/bjoernager/librum/-/raw/master/doc-icon.svg")] + +//! This crate implements procedural macros for [`Librum`](https://crates.io/crates/librum/). + +// For use in macros: +extern crate self as librum_macros; + +macro_rules! use_mod { + ($vis:vis $name:ident) => { + mod $name; + $vis use $name::*; + }; +} +pub(crate) use use_mod; + +use_mod!(discriminants); +use_mod!(generic_name); +use_mod!(impl_derive_macro); +use_mod!(repr); + +mod impls; + +use proc_macro::TokenStream; +use quote::quote; +use syn::{DeriveInput, parse2}; + +#[proc_macro_derive(Decode)] +pub fn derive_decode(input: TokenStream) -> TokenStream { + let input = syn::parse_macro_input!(input as DeriveInput); + + let output = impl_derive_macro( + input, + parse2(quote! { ::librum::Decode }).unwrap(), + None, + impls::decode_struct, + impls::decode_enum, + ); + + //panic!("{output}"); + + output.into() +} + +#[proc_macro_derive(Encode)] +pub fn derive_encode(input: TokenStream) -> TokenStream { + let input = syn::parse_macro_input!(input as DeriveInput); + + let output = impl_derive_macro( + input, + parse2(quote! { ::librum::Encode }).unwrap(), + None, + impls::encode_struct, + impls::encode_enum, + ); + + //panic!("{output}"); + + output.into() +} + +#[proc_macro_derive(SizedEncode)] +pub fn derive_sized_encode(input: TokenStream) -> TokenStream { + let input = syn::parse_macro_input!(input as DeriveInput); + + let output = impl_derive_macro( + input, + parse2(quote! { ::librum::SizedEncode }).unwrap(), + None, + impls::sized_encode_struct, + impls::sized_encode_enum, + ); + + //panic!("{output}"); + + output.into() +} diff --git a/librum-macros/src/repr/mod.rs b/librum-macros/src/repr/mod.rs new file mode 100644 index 0000000..e87f8c5 --- /dev/null +++ b/librum-macros/src/repr/mod.rs @@ -0,0 +1,157 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Librum. +// +// Librum 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. +// +// Librum 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 Librum. If +// not, see . + +use proc_macro2::{Span, TokenStream}; +use quote::ToTokens; +use std::iter; +use syn::{ + Attribute, + Ident, + Path, + PathSegment, + Type, + TypePath, +}; + +/// A derivable enumeration representation. +/// +/// Any type can, *in theory*, be used as a discriminant. +/// This type, however, only includes primitives. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(u8)] +pub enum Repr { + U8, + I8, + U16, + I16, + U32, + I32, + U64, + I64, + U128, + I128, + Usize, + Isize, +} + +impl Repr { + #[inline] + #[must_use] + pub fn get(attrs: &[Attribute]) -> Option { + let mut this = None; + + for attr in attrs { + if attr.path().is_ident("repr") { + attr.parse_nested_meta(|meta| { + use Repr::*; + + let ident = meta.path.require_ident()?; + + if ident == "u8" { this = Some(U8) } + else if ident == "i8" { this = Some(I8) } + else if ident == "u16" { this = Some(U16) } + else if ident == "i16" { this = Some(I16) } + else if ident == "u32" { this = Some(U32) } + else if ident == "i32" { this = Some(I32) } + else if ident == "u64" { this = Some(U64) } + else if ident == "i64" { this = Some(I64) } + else if ident == "u128" { this = Some(U128) } + else if ident == "i128" { this = Some(I128) } + else if ident == "usize" { this = Some(Usize) } + else if ident == "isize" { this = Some(Isize) } + else { panic!("`{ident}` is not a derivable enumeration representation") }; + + Ok(()) + }).unwrap(); + } + + // Ignore all other attributes. + } + + this + } + + #[inline] + #[must_use] + pub const fn to_str(self) -> &'static str { + use Repr::*; + + match self { + U8 => "u8", + I8 => "i8", + U16 => "u16", + I16 => "i16", + U32 => "u32", + I32 => "i32", + U64 => "u64", + I64 => "i64", + U128 => "u128", + I128 => "i128", + Usize => "usize", + Isize => "isize", + } + } + + #[inline(always)] + #[must_use] + pub fn to_ident(self, span: Span) -> Ident { + let ident = self.to_str(); + + Ident::new(ident, span) + } + + #[inline(always)] + #[must_use] + pub fn to_path(self, span: Span) -> Path { + let ident = self.to_ident(span); + + Path { + leading_colon: None, + segments: iter::once(PathSegment { + ident, + arguments: Default::default(), + }).collect(), + } + } + + #[inline] + #[must_use] + pub fn to_type(self, span: Span) -> Type { + Type::Path(TypePath { + qself: None, + path: self.to_path(span), + }) + } +} + +impl Default for Repr { + #[inline(always)] + fn default() -> Self { + Self::Isize + } +} + +impl ToTokens for Repr { + #[inline(always)] + fn to_tokens(&self, tokens: &mut TokenStream) { + self.to_ident(Span::call_site()).to_tokens(tokens); + } +} diff --git a/bzipper.svg b/librum.svg similarity index 100% rename from bzipper.svg rename to librum.svg diff --git a/bzipper/Cargo.toml b/librum/Cargo.toml similarity index 58% rename from bzipper/Cargo.toml rename to librum/Cargo.toml index 5912aa5..687a206 100644 --- a/bzipper/Cargo.toml +++ b/librum/Cargo.toml @@ -1,9 +1,9 @@ [package] -name = "bzipper" -version = "0.11.0" +name = "librum" +version = "0.12.0" edition = "2021" rust-version = "1.83" -documentation = "https://docs.rs/bzipper/" +documentation = "https://docs.rs/librum/" authors.workspace = true description.workspace = true @@ -18,13 +18,14 @@ categories.workspace = true all-features = true [features] -default = ["alloc", "std"] +default = ["alloc", "proc-macro", "std"] -alloc = [] -std = [] +alloc = [] +proc-macro = ["librum-macros"] +std = [] [dependencies] -bzipper_macros = { path = "../bzipper_macros", version = "0.11.0" } +librum-macros = { path = "../librum-macros", version = "0.12.0", optional = true} [lints] workspace = true diff --git a/bzipper/src/buf/mod.rs b/librum/src/buf/mod.rs similarity index 93% rename from bzipper/src/buf/mod.rs rename to librum/src/buf/mod.rs index d567760..f788917 100644 --- a/bzipper/src/buf/mod.rs +++ b/librum/src/buf/mod.rs @@ -1,26 +1,26 @@ // Copyright 2024 Gabriel Bjørnager Jensen. // -// This file is part of bZipper. +// This file is part of Librum. // -// bZipper is free software: you can redistribute -// it and/or modify it under the terms of the GNU +// Librum 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 +// Librum 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 +// er General Public License along with Librum. If // not, see . #[cfg(test)] -mod test; +mod tests; use crate::{ Decode, @@ -29,7 +29,6 @@ use crate::{ OStream, SizedEncode, }; -use crate::error::{DecodeError, EncodeError}; use alloc::boxed::Box; use alloc::vec; @@ -52,9 +51,15 @@ use core::slice::{self, SliceIndex}; /// Create a buffer for holding a `Request` enumeration: /// /// ``` -/// use bzipper::{Buf, SizedEncode, SizedStr}; +/// use librum::{ +/// Buf, +/// Encode, +/// OStream, +/// SizedEncode, +/// SizedStr, +/// }; /// -/// #[derive(SizedEncode)] +/// #[derive(Debug, Encode, SizedEncode)] /// enum Request { /// Join { username: SizedStr<0x40> }, /// @@ -264,13 +269,13 @@ impl Buf { impl Buf { /// Encodes an object into the buffer. /// - /// The object is encoded as by being passed to ::[encode](Encode::encode). + /// The object is encoded as by being passed to <T as [Encode]>::[encode](Encode::encode). /// /// # Errors /// /// Any error that occurs during encoding is passed on and returned from this method. #[inline] - pub fn write>(&mut self, value: U) -> Result<(), EncodeError> { + pub fn write>(&mut self, value: U) -> Result<(), T::Error> { let mut stream = OStream::new(&mut self.buf); value.borrow().encode(&mut stream)?; @@ -285,7 +290,7 @@ impl Buf { impl Buf { /// Decodes an object from the buffer. /// - /// This is done as by passing the contained bytes to ::[decode](Decode::decode). + /// This is done as by passing the contained bytes to <T as [Decode]>::[decode](Decode::decode). /// /// Note that only the bytes specified by [`len`](Self::len) are passed in this call. /// See [`as_slice`](Self::as_slice) for more information. @@ -294,7 +299,7 @@ impl Buf { /// /// Any error that occurs during decoding is passed on and returned from this method. #[inline] - pub fn read(&self) -> Result { + pub fn read(&self) -> Result { // We should only pass the used part of the buffer // to `deserialise`. diff --git a/bzipper/src/buf/test.rs b/librum/src/buf/tests.rs similarity index 74% rename from bzipper/src/buf/test.rs rename to librum/src/buf/tests.rs index eec22df..ebf6764 100644 --- a/bzipper/src/buf/test.rs +++ b/librum/src/buf/tests.rs @@ -1,26 +1,26 @@ // Copyright 2024 Gabriel Bjørnager Jensen. // -// This file is part of bZipper. +// This file is part of Librum. // -// bZipper is free software: you can redistribute -// it and/or modify it under the terms of the GNU +// Librum 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 +// Librum 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 +// er General Public License along with Librum. If // not, see . -use bzipper::Buf; -use bzipper::error::DecodeError; +use librum::Buf; +use librum::error::CharDecodeError; #[test] fn test_buf_write_read() { @@ -40,7 +40,7 @@ fn test_buf_write_read() { assert_eq!(buf, [0x00, 0x01, 0xF4, 0x4D].as_slice()); buf.copy_from_slice(&[0x00, 0x00, 0xD8, 0x00]); - test_read!(Err(DecodeError::InvalidCodePoint(0xD800))); + test_read!(Err(CharDecodeError { code_point: 0xD800 })); buf.copy_from_slice(&[0x00, 0x00, 0xFF, 0x3A]); test_read!(Ok('\u{FF3A}')); diff --git a/bzipper/src/decode/mod.rs b/librum/src/decode/mod.rs similarity index 56% rename from bzipper/src/decode/mod.rs rename to librum/src/decode/mod.rs index b7ca717..e74be08 100644 --- a/bzipper/src/decode/mod.rs +++ b/librum/src/decode/mod.rs @@ -1,29 +1,38 @@ // Copyright 2024 Gabriel Bjørnager Jensen. // -// This file is part of bZipper. +// This file is part of Librum. // -// bZipper is free software: you can redistribute -// it and/or modify it under the terms of the GNU +// Librum 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 +// Librum 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 +// er General Public License along with Librum. If // not, see . #[cfg(test)] -mod test; +mod tests; -use crate::{IStream, SizedEncode}; -use crate::error::{DecodeError, Utf8Error}; +use crate::{DecodeBorrowed, IStream, SizedEncode}; +use crate::error::{ + BoolDecodeError, + CStringDecodeError, + CharDecodeError, + CollectionDecodeError, + EnumDecodeError, + ItemDecodeError, + SystemTimeDecodeError, + Utf8Error, +}; use core::cell::{Cell, RefCell}; use core::convert::Infallible; @@ -52,6 +61,9 @@ use core::ptr::copy_nonoverlapping; use core::str; use core::time::Duration; +#[cfg(feature = "alloc")] +use alloc::borrow::{Cow, ToOwned}; + #[cfg(feature = "alloc")] use alloc::boxed::Box; @@ -70,7 +82,7 @@ use alloc::vec::Vec; #[cfg(feature = "alloc")] use alloc::rc::Rc; -#[cfg(feature = "alloc")] +#[cfg(all(feature = "alloc", target_has_atomic = "ptr"))] use alloc::sync::Arc; #[cfg(feature = "std")] @@ -89,41 +101,47 @@ use std::time::{SystemTime, UNIX_EPOCH}; /// Denotes a type capable of being decoded. pub trait Decode: Sized { + type Error; + /// Decodes an object from the provided stream. /// /// # Errors /// /// If decoding fails due to e.g. an invalid byte sequence in the stream, then an error should be returned. - fn decode(stream: &mut IStream) -> Result; + fn decode(stream: &mut IStream) -> Result; } /// Implemented for tuples with up to twelve members. #[cfg_attr(doc, doc(fake_variadic))] -impl Decode for (T, ) -where - T: Decode, { +impl Decode for (T, ) { + type Error = T::Error; + #[inline(always)] - fn decode(stream: &mut IStream) -> Result { + fn decode(stream: &mut IStream) -> Result { let value = (Decode::decode(stream)?, ); Ok(value) } } impl Decode for [T; N] { + type Error = CollectionDecodeError>; + #[inline] - fn decode(stream: &mut IStream) -> Result { + fn decode(stream: &mut IStream) -> Result { // Initialise the array incrementally. // SAFETY: Always safe. let mut buf: [MaybeUninit; N] = unsafe { MaybeUninit::uninit().assume_init() }; - for item in &mut buf { - let value = Decode::decode(stream)?; + for (i, item) in buf.iter_mut().enumerate() { + let value = Decode::decode(stream) + .map_err(|e| CollectionDecodeError::Item(ItemDecodeError { index: i, error: e }))?; + item.write(value); } - // This should be safe as `MaybeUninit` is - // transparent to `T`, and we have initialised + // SAFETY: This should be safe as `MaybeUninit` + // is transparent to `T` and we have initialised // every element. The original buffer is NOT // dropped automatically, so we can just forget // about it from this point on. `transmute` cannot @@ -134,47 +152,60 @@ impl Decode for [T; N] { } } -#[cfg(feature = "alloc")] -#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +#[cfg(all(feature = "alloc", target_has_atomic = "ptr"))] +#[cfg_attr(doc, doc(cfg(all(feature = "alloc", target_has_atomic = "ptr"))))] impl Decode for Arc { + type Error = T::Error; + #[inline(always)] - fn decode(stream: &mut IStream) -> Result { - Ok(Self::new(Decode::decode(stream)?)) + fn decode(stream: &mut IStream) -> Result { + let value = Decode::decode(stream)?; + + let this = Self::new(value); + Ok(this) } } impl Decode for bool { + type Error = BoolDecodeError; + #[inline] - fn decode(stream: &mut IStream) -> Result { - let value = u8::decode(stream)?; + fn decode(stream: &mut IStream) -> Result { + let value = u8::decode(stream).unwrap(); match value { 0x0 => Ok(false), 0x1 => Ok(true), - _ => Err(DecodeError::InvalidBoolean(value)) + _ => Err(BoolDecodeError { value }) } } } impl Decode for Bound { + type Error = EnumDecodeError; + #[inline(always)] - fn decode(stream: &mut IStream) -> Result { - let discriminant = u8::decode(stream)?; + fn decode(stream: &mut IStream) -> Result { + let discriminant = u8::decode(stream).unwrap(); let this = match discriminant { 0x0 => { - let bound = Decode::decode(stream)?; + let bound = Decode::decode(stream) + .map_err(EnumDecodeError::Field)?; + Self::Included(bound) } 0x1 => { - let bound = Decode::decode(stream)?; + let bound = Decode::decode(stream) + .map_err(EnumDecodeError::Field)?; + Self::Excluded(bound) } 0x2 => Self::Unbounded, - _ => return Err(DecodeError::InvalidDiscriminant(discriminant.into())), + value => return Err(EnumDecodeError::UnassignedDiscriminant { value }), }; Ok(this) @@ -184,8 +215,10 @@ impl Decode for Bound { #[cfg(feature = "alloc")] #[cfg_attr(doc, doc(cfg(feature = "alloc")))] impl Decode for Box { + type Error = T::Error; + #[inline(always)] - fn decode(stream: &mut IStream) -> Result { + fn decode(stream: &mut IStream) -> Result { let value = Decode::decode(stream)?; let this = Self::new(value); @@ -194,8 +227,10 @@ impl Decode for Box { } impl Decode for Cell { + type Error = T::Error; + #[inline(always)] - fn decode(stream: &mut IStream) -> Result { + fn decode(stream: &mut IStream) -> Result { let value = Decode::decode(stream)?; let this = Self::new(value); @@ -204,13 +239,15 @@ impl Decode for Cell { } impl Decode for char { - #[inline] - fn decode(stream: &mut IStream) -> Result { - let value = u32::decode(stream)?; + type Error = CharDecodeError; - let this = value + #[inline] + fn decode(stream: &mut IStream) -> Result { + let code_point = u32::decode(stream).unwrap(); + + let this = code_point .try_into() - .map_err(|_| DecodeError::InvalidCodePoint(value))?; + .map_err(|_| CharDecodeError { code_point })?; Ok(this) } @@ -218,15 +255,35 @@ impl Decode for char { #[cfg(feature = "alloc")] #[cfg_attr(doc, doc(cfg(feature = "alloc")))] -impl Decode for CString { +impl Decode for Cow<'_, B> +where + T: DecodeBorrowed, + B: ToOwned, +{ + type Error = T::Error; + #[inline(always)] - fn decode(stream: &mut IStream) -> Result { - let len = Decode::decode(stream)?; + fn decode(stream: &mut IStream) -> Result { + let value = Decode::decode(stream)?; + + let this = Self::Owned(value); + Ok(this) + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl Decode for CString { + type Error = CStringDecodeError; + + #[inline(always)] + fn decode(stream: &mut IStream) -> Result { + let len = Decode::decode(stream).unwrap(); let data = stream.read(len); for (i, c) in data.iter().enumerate() { - if *c == b'\x00' { return Err(DecodeError::NullCString { index: i }) }; + if *c == b'\x00' { return Err(CStringDecodeError { index: i }) }; } let mut buf = Vec::with_capacity(len); @@ -246,8 +303,10 @@ impl Decode for CString { } impl Decode for Duration { + type Error = Infallible; + #[inline(always)] - fn decode(stream: &mut IStream) -> Result { + fn decode(stream: &mut IStream) -> Result { let secs = Decode::decode(stream)?; let nanos = Decode::decode(stream)?; @@ -258,21 +317,26 @@ impl Decode for Duration { #[cfg(feature = "std")] #[cfg_attr(doc, doc(cfg(feature = "std")))] -impl Decode for HashMap +impl Decode for HashMap where - K: Decode + Eq + Hash, - V: Decode, + K: Decode + Eq + Hash, + V: Decode, S: BuildHasher + Default, - { +{ + type Error = CollectionDecodeError>; + #[inline] - fn decode(stream: &mut IStream) -> Result { - let len = Decode::decode(stream)?; + fn decode(stream: &mut IStream) -> Result { + let len = Decode::decode(stream).unwrap(); let mut this = Self::with_capacity_and_hasher(len, Default::default()); - for _ in 0x0..len { - let key = Decode::decode(stream)?; - let value = Decode::decode(stream)?; + for i in 0x0..len { + let key= Decode::decode(stream) + .map_err(|e| CollectionDecodeError::Item(ItemDecodeError { index: i, error: e }))?; + + let value = Decode::decode(stream) + .map_err(|e| CollectionDecodeError::Item(ItemDecodeError { index: i, error: e }))?; this.insert(key, value); } @@ -283,19 +347,22 @@ where #[cfg(feature = "std")] #[cfg_attr(doc, doc(cfg(feature = "std")))] -impl Decode for HashSet +impl Decode for HashSet where - T: Decode + Eq + Hash, + K: Decode + Eq + Hash, S: BuildHasher + Default, - { +{ + type Error = CollectionDecodeError>; + #[inline] - fn decode(stream: &mut IStream) -> Result { - let len = Decode::decode(stream)?; + fn decode(stream: &mut IStream) -> Result { + let len = Decode::decode(stream).unwrap(); let mut this = Self::with_capacity_and_hasher(len, Default::default()); - for _ in 0x0..len { - let key = Decode::decode(stream)?; + for i in 0x0..len { + let key = Decode::decode(stream) + .map_err(|e| CollectionDecodeError::Item(ItemDecodeError { index: i, error: e }) )?; this.insert(key); } @@ -305,23 +372,27 @@ where } impl Decode for Infallible { - #[expect(clippy::panic_in_result_fn)] + type Error = Self; + #[inline(always)] - fn decode(_stream: &mut IStream) -> Result { + fn decode(_stream: &mut IStream) -> Result { panic!("cannot deserialise `Infallible` as it cannot be serialised to begin with") } } impl Decode for IpAddr { + type Error = EnumDecodeError; + #[inline] - fn decode(stream: &mut IStream) -> Result { - let discriminant = u8::decode(stream)?; + fn decode(stream: &mut IStream) -> Result { + let discriminant = u8::decode(stream) + .map_err(EnumDecodeError::InvalidDiscriminant)?; let this = match discriminant { - 0x4 => Self::V4(Decode::decode(stream)?), - 0x6 => Self::V6(Decode::decode(stream)?), + 0x4 => Self::V4(Decode::decode(stream).unwrap()), + 0x6 => Self::V6(Decode::decode(stream).unwrap()), - _ => return Err(DecodeError::InvalidDiscriminant(discriminant.into())) + value => return Err(EnumDecodeError::UnassignedDiscriminant { value }) }; Ok(this) @@ -329,24 +400,30 @@ impl Decode for IpAddr { } impl Decode for Ipv4Addr { + type Error = Infallible; + #[inline(always)] - fn decode(stream: &mut IStream) -> Result { + fn decode(stream: &mut IStream) -> Result { let value = Decode::decode(stream)?; Ok(Self::from_bits(value)) } } impl Decode for Ipv6Addr { + type Error = Infallible; + #[inline(always)] - fn decode(stream: &mut IStream) -> Result { + fn decode(stream: &mut IStream) -> Result { let value = Decode::decode(stream)?; Ok(Self::from_bits(value)) } } impl Decode for isize { + type Error = Infallible; + #[inline(always)] - fn decode(stream: &mut IStream) -> Result { + fn decode(stream: &mut IStream) -> Result { let value = i16::decode(stream)?; Ok(value as Self) } @@ -355,14 +432,17 @@ impl Decode for isize { #[cfg(feature = "alloc")] #[cfg_attr(doc, doc(cfg(feature = "alloc")))] impl Decode for LinkedList { + type Error = CollectionDecodeError>; + #[inline] - fn decode(stream: &mut IStream) -> Result { - let len = usize::decode(stream)?; + fn decode(stream: &mut IStream) -> Result { + let len = usize::decode(stream).unwrap(); let mut this = Self::new(); - for _ in 0x0..len { - let value = T::decode(stream)?; + for i in 0x0..len { + let value = T::decode(stream) + .map_err(|e| CollectionDecodeError::Item(ItemDecodeError { index: i, error: e }))?; this.push_back(value); } @@ -374,17 +454,21 @@ impl Decode for LinkedList { #[cfg(feature = "std")] #[cfg_attr(doc, doc(cfg(feature = "std")))] impl Decode for Mutex { + type Error = T::Error; + #[inline(always)] - fn decode(stream: &mut IStream) -> Result { + fn decode(stream: &mut IStream) -> Result { Ok(Self::new(Decode::decode(stream)?)) } } impl Decode for Option { + type Error = T::Error; + #[expect(clippy::if_then_some_else_none)] // ??? #[inline] - fn decode(stream: &mut IStream) -> Result { - let sign = bool::decode(stream)?; + fn decode(stream: &mut IStream) -> Result { + let sign = bool::decode(stream).unwrap(); let this = if sign { Some(Decode::decode(stream)?) @@ -397,22 +481,28 @@ impl Decode for Option { } impl Decode for PhantomData { + type Error = Infallible; + #[inline(always)] - fn decode(_stream: &mut IStream) -> Result { + fn decode(_stream: &mut IStream) -> Result { Ok(Self) } } impl Decode for PhantomPinned { + type Error = Infallible; + #[inline(always)] - fn decode(_stream: &mut IStream) -> Result { + fn decode(_stream: &mut IStream) -> Result { Ok(Self) } } impl Decode for Range { + type Error = T::Error; + #[inline(always)] - fn decode(stream: &mut IStream) -> Result { + fn decode(stream: &mut IStream) -> Result { let start = Decode::decode(stream)?; let end = Decode::decode(stream)?; @@ -421,8 +511,10 @@ impl Decode for Range { } impl Decode for RangeFrom { + type Error = T::Error; + #[inline(always)] - fn decode(stream: &mut IStream) -> Result { + fn decode(stream: &mut IStream) -> Result { let start = Decode::decode(stream)?; Ok(start..) @@ -430,15 +522,19 @@ impl Decode for RangeFrom { } impl Decode for RangeFull { + type Error = Infallible; + #[inline(always)] - fn decode(_stream: &mut IStream) -> Result { + fn decode(_stream: &mut IStream) -> Result { Ok(..) } } impl Decode for RangeInclusive { + type Error = T::Error; + #[inline(always)] - fn decode(stream: &mut IStream) -> Result { + fn decode(stream: &mut IStream) -> Result { let start = Decode::decode(stream)?; let end = Decode::decode(stream)?; @@ -447,8 +543,10 @@ impl Decode for RangeInclusive { } impl Decode for RangeTo { + type Error = T::Error; + #[inline(always)] - fn decode(stream: &mut IStream) -> Result { + fn decode(stream: &mut IStream) -> Result { let end = Decode::decode(stream)?; Ok(..end) @@ -456,8 +554,10 @@ impl Decode for RangeTo { } impl Decode for RangeToInclusive { + type Error = T::Error; + #[inline(always)] - fn decode(stream: &mut IStream) -> Result { + fn decode(stream: &mut IStream) -> Result { let end = Decode::decode(stream)?; Ok(..=end) @@ -467,15 +567,19 @@ impl Decode for RangeToInclusive { #[cfg(feature = "alloc")] #[cfg_attr(doc, doc(cfg(feature = "alloc")))] impl Decode for Rc { + type Error = T::Error; + #[inline(always)] - fn decode(stream: &mut IStream) -> Result { + fn decode(stream: &mut IStream) -> Result { Ok(Self::new(Decode::decode(stream)?)) } } impl Decode for RefCell { + type Error = T::Error; + #[inline(always)] - fn decode(stream: &mut IStream) -> Result { + fn decode(stream: &mut IStream) -> Result { let value = Decode::decode(stream)?; let this = Self::new(value); @@ -483,15 +587,28 @@ impl Decode for RefCell { } } -impl Decode for core::result::Result { +impl Decode for core::result::Result +where + T: Decode, + E: Decode, +{ + type Error = EnumDecodeError; + #[inline] - fn decode(stream: &mut IStream) -> Result { - let sign = bool::decode(stream)?; + fn decode(stream: &mut IStream) -> Result { + let sign = bool::decode(stream) + .map_err(EnumDecodeError::InvalidDiscriminant)?; let this = if sign { - Err(E::decode(stream)?) + let value = Decode::decode(stream) + .map_err(EnumDecodeError::Field)?; + + Err(value) } else { - Ok(Decode::decode(stream)?) + let value = Decode::decode(stream) + .map_err(EnumDecodeError::Field)?; + + Ok(value) }; Ok(this) @@ -501,29 +618,41 @@ impl Decode for core::result::Result { #[cfg(feature = "std")] #[cfg_attr(doc, doc(cfg(feature = "std")))] impl Decode for RwLock { + type Error = T::Error; + #[inline(always)] - fn decode(stream: &mut IStream) -> Result { - Ok(Self::new(Decode::decode(stream)?)) + fn decode(stream: &mut IStream) -> Result { + let value = Decode::decode(stream)?; + + let this = Self::new(value); + Ok(this) } } impl Decode for Saturating { + type Error = T::Error; + #[inline(always)] - fn decode(stream: &mut IStream) -> Result { - Ok(Self(Decode::decode(stream)?)) + fn decode(stream: &mut IStream) -> Result { + let value = Decode::decode(stream)?; + + let this = Self(value); + Ok(this) } } impl Decode for SocketAddr { + type Error = EnumDecodeError; + #[inline] - fn decode(stream: &mut IStream) -> Result { - let discriminant = u8::decode(stream)?; + fn decode(stream: &mut IStream) -> Result { + let discriminant = u8::decode(stream).unwrap(); let this = match discriminant { - 0x4 => Self::V4(Decode::decode(stream)?), - 0x6 => Self::V6(Decode::decode(stream)?), + 0x4 => Self::V4(Decode::decode(stream).unwrap()), + 0x6 => Self::V6(Decode::decode(stream).unwrap()), - _ => return Err(DecodeError::InvalidDiscriminant(discriminant.into())) + value => return Err(EnumDecodeError::UnassignedDiscriminant { value }) }; Ok(this) @@ -531,8 +660,10 @@ impl Decode for SocketAddr { } impl Decode for SocketAddrV4 { + type Error = Infallible; + #[inline(always)] - fn decode(stream: &mut IStream) -> Result { + fn decode(stream: &mut IStream) -> Result { let ip = Decode::decode(stream)?; let port = Decode::decode(stream)?; @@ -542,8 +673,10 @@ impl Decode for SocketAddrV4 { } impl Decode for SocketAddrV6 { + type Error = Infallible; + #[inline(always)] - fn decode(stream: &mut IStream) -> Result { + fn decode(stream: &mut IStream) -> Result { let ip = Decode::decode(stream)?; let port = Decode::decode(stream)?; let flow_info = Decode::decode(stream)?; @@ -557,9 +690,11 @@ impl Decode for SocketAddrV6 { #[cfg(feature = "alloc")] #[cfg_attr(doc, doc(cfg(feature = "alloc")))] impl Decode for String { + type Error = CollectionDecodeError; + #[inline(always)] - fn decode(stream: &mut IStream) -> Result { - let len = Decode::decode(stream)?; + fn decode(stream: &mut IStream) -> Result { + let len = Decode::decode(stream).unwrap(); let data = stream.read(len); @@ -568,7 +703,7 @@ impl Decode for String { let i = e.valid_up_to(); let c = data[i]; - DecodeError::BadString(Utf8Error { value: c, index: i }) + CollectionDecodeError::Item(Utf8Error { value: c, index: i }) })?; let mut v = Vec::with_capacity(len); @@ -590,9 +725,11 @@ impl Decode for String { #[cfg(feature = "std")] #[cfg_attr(doc, doc(cfg(feature = "std")))] impl Decode for SystemTime { + type Error = SystemTimeDecodeError; + #[inline] - fn decode(stream: &mut IStream) -> Result { - let time = i64::decode(stream)?; + fn decode(stream: &mut IStream) -> Result { + let time = i64::decode(stream).unwrap(); let this = if time.is_positive() { let time = time as u64; @@ -604,20 +741,24 @@ impl Decode for SystemTime { UNIX_EPOCH.checked_sub(Duration::from_secs(time)) }; - this.ok_or_else(|| DecodeError::NarrowSystemTime { timestamp: time }) + this.ok_or(SystemTimeDecodeError { timestamp: time }) } } impl Decode for () { + type Error = Infallible; + #[inline(always)] - fn decode(_stream: &mut IStream) -> Result { + fn decode(_stream: &mut IStream) -> Result { Ok(()) } } impl Decode for usize { + type Error = Infallible; + #[inline(always)] - fn decode(stream: &mut IStream) -> Result { + fn decode(stream: &mut IStream) -> Result { let value = u16::decode(stream)?; Ok(value as Self) } @@ -626,15 +767,18 @@ impl Decode for usize { #[cfg(feature = "alloc")] #[cfg_attr(doc, doc(cfg(feature = "alloc")))] impl Decode for Vec { + type Error = CollectionDecodeError>; + #[inline] - fn decode(stream: &mut IStream) -> Result { - let len = Decode::decode(stream)?; + fn decode(stream: &mut IStream) -> Result { + let len = Decode::decode(stream).unwrap(); let mut this = Self::with_capacity(len); let buf = this.as_mut_ptr(); for i in 0x0..len { - let value = Decode::decode(stream)?; + let value = Decode::decode(stream) + .map_err(|e| CollectionDecodeError::Item(ItemDecodeError { index: i, error: e }))?; // SAFETY: Each index is within bounds (i.e. capac- // ity). @@ -649,17 +793,24 @@ impl Decode for Vec { } impl Decode for Wrapping { + type Error = T::Error; + #[inline(always)] - fn decode(stream: &mut IStream) -> Result { - Ok(Self(Decode::decode(stream)?)) + fn decode(stream: &mut IStream) -> Result { + let value = Decode::decode(stream)?; + + let this = Self(value); + Ok(this) } } macro_rules! impl_numeric { ($ty:ty$(,)?) => { - impl ::bzipper::Decode for $ty { + impl ::librum::Decode for $ty { + type Error = Infallible; + #[inline] - fn decode(stream: &mut IStream) -> ::core::result::Result { + fn decode(stream: &mut IStream) -> ::core::result::Result { let data = stream .read(Self::MAX_ENCODED_SIZE) .try_into() @@ -677,11 +828,15 @@ macro_rules! impl_tuple { $($tys:ident),+$(,)? } => { #[doc(hidden)] - impl<$($tys: ::bzipper::Decode, )*> ::bzipper::Decode for ($($tys, )*) { + impl<$($tys, )* E> ::librum::Decode for ($($tys, )*) + where + $($tys: Decode, )* { + type Error = E; + #[inline(always)] - fn decode(stream: &mut ::bzipper::IStream) -> ::core::result::Result { + fn decode(stream: &mut ::librum::IStream) -> ::core::result::Result { let this = ( - $( <$tys as ::bzipper::Decode>::decode(stream)?, )* + $( <$tys as ::librum::Decode>::decode(stream)?, )* ); Ok(this) @@ -692,13 +847,15 @@ macro_rules! impl_tuple { macro_rules! impl_non_zero { ($ty:ty$(,)?) => { - impl ::bzipper::Decode for NonZero<$ty> { + impl ::librum::Decode for NonZero<$ty> { + type Error = ::librum::error::NonZeroDecodeError; + #[inline] - fn decode(stream: &mut IStream) -> ::core::result::Result { - let value = <$ty as ::bzipper::Decode>::decode(stream)?; + fn decode(stream: &mut IStream) -> ::core::result::Result { + let value = <$ty as ::librum::Decode>::decode(stream).unwrap(); let this = NonZero::new(value) - .ok_or(::bzipper::error::DecodeError::NullInteger)?; + .ok_or(::librum::error::NonZeroDecodeError)?; Ok(this) } @@ -713,10 +870,15 @@ macro_rules! impl_atomic { } => { #[cfg(target_has_atomic = $width)] #[cfg_attr(doc, doc(cfg(target_has_atomic = $width)))] - impl ::bzipper::Decode for $ty { + impl ::librum::Decode for $ty { + type Error = ::core::convert::Infallible; + #[inline(always)] - fn decode(stream: &mut ::bzipper::IStream) -> ::core::result::Result { - Ok(Self::new(::bzipper::Decode::decode(stream)?)) + fn decode(stream: &mut ::librum::IStream) -> ::core::result::Result { + let value = ::librum::Decode::decode(stream).unwrap(); + + let this = Self::new(value); + Ok(this) } } }; @@ -819,32 +981,32 @@ impl_tuple! { } impl_tuple! { - T0, - T1, - T2, - T3, - T4, - T5, - T6, - T7, - T8, - T9, - T10, + T0, + T1, + T2, + T3, + T4, + T5, + T6, + T7, + T8, + T9, + T10, } impl_tuple! { - T0, - T1, - T2, - T3, - T4, - T5, - T6, - T7, - T8, - T9, - T10, - T11, + T0, + T1, + T2, + T3, + T4, + T5, + T6, + T7, + T8, + T9, + T10, + T11, } impl_non_zero!(i128); diff --git a/bzipper/src/decode/test.rs b/librum/src/decode/tests.rs similarity index 78% rename from bzipper/src/decode/test.rs rename to librum/src/decode/tests.rs index 2135114..462f141 100644 --- a/bzipper/src/decode/test.rs +++ b/librum/src/decode/tests.rs @@ -1,61 +1,42 @@ // Copyright 2024 Gabriel Bjørnager Jensen. // -// This file is part of bZipper. +// This file is part of Librum. // -// bZipper is free software: you can redistribute -// it and/or modify it under the terms of the GNU +// Librum 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 +// Librum 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 +// er General Public License along with Librum. If // not, see . -use alloc::vec::Vec; -use alloc::string::String; -use bzipper::{Decode, IStream, SizedEncode}; -use core::char; +use librum::{Decode, Encode, IStream, SizedEncode}; +use std::char; +use std::vec::Vec; +use std::string::String; + +macro_rules! test { + ($ty:ty: $data:expr => $value:expr) => {{ + let mut stream = IStream::new(&$data); + + let left = <$ty as Decode>::decode(&mut stream).unwrap(); + let right = $value; + + assert_eq!(left, right); + }}; +} #[test] fn test_decode() { - #[derive(Debug, Decode, PartialEq, SizedEncode)] - struct ProcExit { - exit_code: i32, - timestmap: u64, - } - - #[derive(Debug, Decode, PartialEq, SizedEncode)] - struct NewByte(u8); - - #[derive(Debug, Decode, PartialEq, SizedEncode)] - struct Unit; - - #[derive(Debug, Decode, PartialEq, SizedEncode)] - enum UnitOrFields { - Unit, - Unnamed(i32), - Named { timestamp: u64 }, - } - - macro_rules! test { - ($ty:ty: $data:expr => $value:expr) => {{ - let mut stream = IStream::new(&$data); - - let left = <$ty as Decode>::decode(&mut stream).unwrap(); - let right = $value; - - assert_eq!(left, right); - }}; - } - test!(i8: [0x00] => 0x00); test!(i8: [0x7F] => 0x7F); test!(i8: [0x80] => -0x80); @@ -95,6 +76,31 @@ fn test_decode() { test!(Result<(), i8>: [0x00, 0x00] => Ok(())); test!(Result<(), i8>: [0x01, 0x7F] => Err(i8::MAX)); + test!(Vec: [0x00, 0x02, 0xAA, 0xBB, 0xCC, 0xDD] => [0xAA_BB, 0xCC_DD].as_slice()); + + test!(String: [0x00, 0x06, 0xE6, 0x97, 0xA5, 0xE6, 0x9C, 0xAC] => "\u{65E5}\u{672C}"); +} + +#[test] +fn test_decode_derive() { + #[derive(Debug, Decode, Encode, PartialEq, SizedEncode)] + struct ProcExit { + exit_code: i32, + timestmap: u64, + } + + #[derive(Debug, Decode, Encode, PartialEq, SizedEncode)] + struct NewByte(u8); + + #[derive(Debug, Decode, Encode, PartialEq, SizedEncode)] + struct Unit; + + #[derive(Debug, Decode, Encode, PartialEq, SizedEncode)] + enum UnitOrFields { + Unit, + Unnamed(i32), + Named { timestamp: u64 }, + } test!(ProcExit: [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x5E, 0x0B, 0xE1, 0x00, @@ -118,8 +124,4 @@ fn test_decode() { 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x66, 0xC5, 0xC8, 0x4C, ] => UnitOrFields::Named { timestamp: 1724237900 }); - - test!(Vec: [0x00, 0x02, 0xAA, 0xBB, 0xCC, 0xDD] => [0xAA_BB, 0xCC_DD].as_slice()); - - test!(String: [0x00, 0x06, 0xE6, 0x97, 0xA5, 0xE6, 0x9C, 0xAC] => "\u{65E5}\u{672C}"); } diff --git a/librum/src/decode_borrowed/mod.rs b/librum/src/decode_borrowed/mod.rs new file mode 100644 index 0000000..2f4c6e1 --- /dev/null +++ b/librum/src/decode_borrowed/mod.rs @@ -0,0 +1,84 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Librum. +// +// Librum 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. +// +// Librum 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 Librum. If +// not, see . + +use crate::Decode; + +use core::borrow::Borrow; + +#[cfg(feature = "alloc")] +use alloc::boxed::Box; + +#[cfg(feature = "alloc")] +use alloc::ffi::CString; + +#[cfg(feature = "alloc")] +use alloc::rc::Rc; + +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + +#[cfg(feature = "alloc")] +use alloc::string::String; + +#[cfg(all(feature = "alloc", target_has_atomic = "ptr"))] +use alloc::sync::Arc; + +/// Indicates a scheme relationship between borrowed and owned types. +/// +/// Implementing this trait is a promise that <Self as [Decode]>::[decode](Decode::decode) can handle any encoding of `B`. +/// This is mainly useful for types that implement [`Encode`](crate::Encode::encode) but do not implement `Decode` for whatever reason (mostly the act of being unsized). +/// +/// The primary user of this trait is the `Decode` implementation of [`Cow`](alloc::borrow::Cow). +/// +/// # Arrays +/// +/// This trait in the form DecodeBorrowed<[\[T\]]> is not implemented for [`[T; N]`](array) for the simple reason that arrays they do not encode their length (as it is hard coded into the type), thus rendering their scheme incompatible with that of slices. +/// +/// [\[T\]]: array +/// +/// An alternative to using arrays would be to use [`SizedSlice`](crate::SizedSlice). +pub trait DecodeBorrowed: Borrow + Decode { } + +impl DecodeBorrowed for T { } + +#[cfg(all(feature = "alloc", target_has_atomic = "ptr"))] +#[cfg_attr(doc, doc(cfg(all(feature = "alloc", target_has_atomic = "ptr"))))] +impl DecodeBorrowed for Arc { } + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl DecodeBorrowed for Box { } + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl DecodeBorrowed for CString { } + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl DecodeBorrowed for Rc { } + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl DecodeBorrowed for String { } + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl DecodeBorrowed<[T]> for Vec { } diff --git a/bzipper/src/encode/mod.rs b/librum/src/encode/mod.rs similarity index 62% rename from bzipper/src/encode/mod.rs rename to librum/src/encode/mod.rs index 03b8dd9..ece7267 100644 --- a/bzipper/src/encode/mod.rs +++ b/librum/src/encode/mod.rs @@ -1,29 +1,36 @@ // Copyright 2024 Gabriel Bjørnager Jensen. // -// This file is part of bZipper. +// This file is part of Librum. // -// bZipper is free software: you can redistribute -// it and/or modify it under the terms of the GNU +// Librum 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 +// Librum 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 +// er General Public License along with Librum. If // not, see . #[cfg(test)] -mod test; +mod tests; use crate::OStream; -use crate::error::EncodeError; +use crate::error::{ + CollectionEncodeError, + EnumEncodeError, + IsizeEncodeError, + ItemEncodeError, + RefCellEncodeError, + UsizeEncodeError, +}; use core::cell::{Cell, LazyCell, RefCell}; use core::convert::Infallible; @@ -72,7 +79,7 @@ use alloc::vec::Vec; #[cfg(feature = "alloc")] use alloc::rc::Rc; -#[cfg(feature = "alloc")] +#[cfg(all(feature = "alloc", target_has_atomic = "ptr"))] use alloc::sync::Arc; #[cfg(feature = "std")] @@ -87,7 +94,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; /// Denotes a type capable of being encoded. /// /// It is recommended to simply derive this trait for custom types. -/// It can, however, also be manually implemented. +/// It can, of course, also just be manually implemented. /// /// If all possible encodings have a known maximum size, then the [`SizedEncode`](crate::SizedEncode) trait should additionally be implemented. /// @@ -100,8 +107,8 @@ use std::time::{SystemTime, UNIX_EPOCH}; /// // plementation is equivalent to what would have /// // been derived. /// -/// use bzipper::{Encode, OStream}; -/// use bzipper::error::EncodeError; +/// use librum::{Encode, OStream}; +/// use core::convert::Infallible; /// /// struct Foo { /// bar: u16, @@ -109,7 +116,11 @@ use std::time::{SystemTime, UNIX_EPOCH}; /// } /// /// impl Encode for Foo { -/// fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { +/// // Both `u16` and `f32` encode infallibly. +/// +/// type Error = Infallible; +/// +/// fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { /// // Encode fields using chaining. /// /// self.bar.encode(stream)?; @@ -120,6 +131,8 @@ use std::time::{SystemTime, UNIX_EPOCH}; /// } /// ``` pub trait Encode { + type Error; + /// Encodes `self` into the provided stream. /// /// # Errors @@ -129,19 +142,23 @@ pub trait Encode { /// # Panics /// /// If `stream` cannot contain the entirety of the resulting encoding, then this method should panic. - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError>; + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error>; } -impl Encode for &T { +impl Encode for &T { + type Error = T::Error; + #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { T::encode(self, stream) } } -impl Encode for &mut T { +impl Encode for &mut T { + type Error = T::Error; + #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { T::encode(self, stream) } } @@ -149,19 +166,25 @@ impl Encode for &mut T { /// Implemented for tuples with up to twelve members. #[cfg_attr(doc, doc(fake_variadic))] impl Encode for (T, ) { + type Error = T::Error; + #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { self.0.encode(stream) } } impl Encode for [T; N] { + type Error = CollectionEncodeError>; + /// Encodes each element sequentially. /// The length is hard-coded into the type and is therefore not encoded. #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { - for value in self { - value.encode(stream)?; + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { + for (i, v) in self.iter().enumerate() { + v + .encode(stream) + .map_err(|e| CollectionEncodeError::Item(ItemEncodeError { index: i, error: e }))?; } Ok(()) @@ -169,51 +192,64 @@ impl Encode for [T; N] { } impl Encode for [T] { + type Error = CollectionEncodeError>; + /// Encodes each element sequentially with an extra length specifier (of type [`usize`]) prepended first. #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { - self.len().encode(stream)?; + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { + self + .len() + .encode(stream) + .map_err(CollectionEncodeError::Length)?; - for value in self { - value.encode(stream)?; + for (i,v) in self.iter().enumerate() { + v + .encode(stream) + .map_err(|e| CollectionEncodeError::Item(ItemEncodeError { index: i, error: e }))?; } Ok(()) } } -#[cfg(feature = "alloc")] -#[cfg_attr(doc, doc(cfg(feature = "alloc")))] -impl Encode for Arc { +#[cfg(all(feature = "alloc", target_has_atomic = "ptr"))] +#[cfg_attr(doc, doc(cfg(all(feature = "alloc", target_has_atomic = "ptr"))))] +impl Encode for Arc { + type Error = T::Error; + #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { T::encode(self, stream) } } impl Encode for bool { + type Error = ::Error; + #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { u8::from(*self).encode(stream) } } impl Encode for Bound { + type Error = EnumEncodeError; + #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { match *self { Self::Included(ref bound) => { - 0x0u8.encode(stream)?; - bound.encode(stream)?; + 0x0u8.encode(stream).unwrap(); + bound.encode(stream).map_err(EnumEncodeError::Field)?; } Self::Excluded(ref bound) => { - 0x1u8.encode(stream)?; - bound.encode(stream)?; + 0x1u8.encode(stream).unwrap(); + bound.encode(stream).map_err(EnumEncodeError::Field)?; } Self::Unbounded => { - 0x2u8.encode(stream)?; + 0x2u8.encode(stream).unwrap(); } } @@ -223,40 +259,50 @@ impl Encode for Bound { #[cfg(feature = "alloc")] #[cfg_attr(doc, doc(cfg(feature = "alloc")))] -impl Encode for Box { +impl Encode for Box { + type Error = T::Error; + #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { T::encode(self, stream) } } -impl Encode for Cell { +impl Encode for Cell { + type Error = T::Error; + #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { self.get().encode(stream) } } impl Encode for char { + type Error = ::Error; + #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { u32::from(*self).encode(stream) } } #[cfg(feature = "alloc")] #[cfg_attr(doc, doc(cfg(feature = "alloc")))] -impl Encode for Cow<'_, T> { +impl Encode for Cow<'_, T> { + type Error = T::Error; + #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { T::encode(self, stream) } } impl Encode for CStr { + type Error = <[u8] as Encode>::Error; + /// Encodes the string identically to [a byte slice](slice) containing the string's byte values **excluding** the null terminator. #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { self.to_bytes().encode(stream) } } @@ -264,19 +310,23 @@ impl Encode for CStr { #[cfg(feature = "alloc")] #[cfg_attr(doc, doc(cfg(feature = "alloc")))] impl Encode for CString { + type Error = ::Error; + /// See the the implementation of [`CStr`]. #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { self.as_c_str().encode(stream) } } impl Encode for Duration { + type Error = Infallible; + /// Encodes the duration's seconds and nanoseconds counters sequentially. #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { - self.as_secs().encode(stream)?; - self.subsec_nanos().encode(stream)?; + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { + self.as_secs().encode(stream).unwrap(); + self.subsec_nanos().encode(stream).unwrap(); Ok(()) } @@ -284,14 +334,16 @@ impl Encode for Duration { #[cfg(feature = "std")] #[cfg_attr(doc, doc(cfg(feature = "std")))] -impl Encode for HashMap +impl Encode for HashMap where - K: Encode, - V: Encode, + K: Encode, + V: Encode, S: BuildHasher, - { +{ + type Error = E; + #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { for (key, value) in self { key.encode(stream)?; value.encode(stream)?; @@ -303,13 +355,15 @@ where #[cfg(feature = "std")] #[cfg_attr(doc, doc(cfg(feature = "std")))] -impl Encode for HashSet +impl Encode for HashSet where - T: Encode, + K: Encode, S: BuildHasher, - { +{ + type Error = K::Error; + #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { for key in self { key.encode(stream)?; } @@ -321,8 +375,10 @@ where // Especially useful for `Result`. // **If** that is even needed, of course. impl Encode for Infallible { + type Error = Self; + #[inline(always)] - fn encode(&self, _stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, _stream: &mut OStream) -> Result<(), Self::Error> { // SAFETY: `Infallible` can **never** be construct- // ed. unsafe { unreachable_unchecked() } @@ -330,22 +386,24 @@ impl Encode for Infallible { } impl Encode for IpAddr { + type Error = EnumEncodeError; + /// Encodes a the address with a preceding discriminant denoting the IP version of the address (i.e. `4` for IPv4 and `6` for IPv6). /// /// See also the implementations of [`Ipv4Addr`] and [`Ipv6Addr`]. #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { // The discriminant here is the IP version. match *self { Self::V4(ref addr) => { - 0x4u8.encode(stream)?; - addr.encode(stream)?; + 0x4u8.encode(stream).map_err(EnumEncodeError::Discriminant)?; + addr.encode(stream).map_err(EnumEncodeError::Field)?; } Self::V6(ref addr) => { - 0x6u8.encode(stream)?; - addr.encode(stream)?; + 0x6u8.encode(stream).map_err(EnumEncodeError::Discriminant)?; + addr.encode(stream).map_err(EnumEncodeError::Field)?; } } @@ -354,39 +412,46 @@ impl Encode for IpAddr { } impl Encode for Ipv4Addr { + type Error = Infallible; + /// Encodes the address's bits in big-endian. #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { let value = self.to_bits(); value.encode(stream) } } impl Encode for Ipv6Addr { + type Error = Infallible; + /// Encodes the address's bits in big-endian. #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { let value = self.to_bits(); value.encode(stream) } } impl Encode for isize { - /// Casts `self` to [`i16`] and encodes. - /// - /// If this conversion isn't possible for the given value, then the [`IsizeOutOfRange`](EncodeError::IsizeOutOfRange) error is returned. - #[inline] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { - let value = i16::try_from(*self) - .map_err(|_| EncodeError::IsizeOutOfRange(*self))?; + type Error = IsizeEncodeError; - value.encode(stream) + /// Casts `self` to [`i16`] and encodes the result. + #[inline] + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { + let value = i16::try_from(*self) + .map_err(|_| IsizeEncodeError(*self))?; + + value.encode(stream).unwrap(); + Ok(()) } } impl Encode for LazyCell { + type Error = T::Error; + #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { T::encode(self, stream) } } @@ -394,19 +459,30 @@ impl Encode for LazyCell { #[cfg(feature = "std")] #[cfg_attr(doc, doc(cfg(feature = "std")))] impl Encode for LazyLock { + type Error = T::Error; + #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { T::encode(self, stream) } } #[cfg(feature = "alloc")] #[cfg_attr(doc, doc(cfg(feature = "alloc")))] -impl Encode for LinkedList { +impl, E> Encode for LinkedList { + type Error = CollectionEncodeError; + #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { - for value in self { - value.encode(stream)?; + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { + self + .len() + .encode(stream) + .map_err(CollectionEncodeError::Length)?; + + for (i, v) in self.iter().enumerate() { + v + .encode(stream) + .map_err(|e| CollectionEncodeError::Item((i, e)))?; } Ok(()) @@ -415,9 +491,11 @@ impl Encode for LinkedList { #[cfg(feature = "std")] #[cfg_attr(doc, doc(cfg(feature = "std")))] -impl Encode for Mutex { +impl Encode for Mutex { + type Error = T::Error; + #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { self .lock() .unwrap_or_else(std::sync::PoisonError::into_inner) @@ -426,16 +504,20 @@ impl Encode for Mutex { } impl Encode for Option { + type Error = T::Error; + /// Encodes a sign denoting the optional's variant. /// This is `false` for `None` instances and `true` for `Some` instances. /// /// If `Some`, then the contained value is encoded after this sign.. - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { match *self { - None => false.encode(stream)?, + None => { + false.encode(stream).unwrap(); + } Some(ref v) => { - true.encode(stream)?; + true.encode(stream).unwrap(); v.encode(stream)?; } }; @@ -445,22 +527,28 @@ impl Encode for Option { } impl Encode for PhantomData { + type Error = Infallible; + #[inline(always)] - fn encode(&self, _stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, _stream: &mut OStream) -> Result<(), Self::Error> { Ok(()) } } impl Encode for PhantomPinned { + type Error = Infallible; + #[inline(always)] - fn encode(&self, _stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, _stream: &mut OStream) -> Result<(), Self::Error> { Ok(()) } } impl Encode for Range { + type Error = T::Error; + #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { self.start.encode(stream)?; self.end.encode(stream)?; @@ -469,22 +557,28 @@ impl Encode for Range { } impl Encode for RangeFrom { + type Error = T::Error; + #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { self.start.encode(stream) } } impl Encode for RangeFull { + type Error = Infallible; + #[inline(always)] - fn encode(&self, _stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, _stream: &mut OStream) -> Result<(), Self::Error> { Ok(()) } } impl Encode for RangeInclusive { + type Error = T::Error; + #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { self.start().encode(stream)?; self.end().encode(stream)?; @@ -493,15 +587,19 @@ impl Encode for RangeInclusive { } impl Encode for RangeTo { + type Error = T::Error; + #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { self.end.encode(stream) } } impl Encode for RangeToInclusive { + type Error = T::Error; + #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { self.end.encode(stream)?; Ok(()) @@ -510,42 +608,54 @@ impl Encode for RangeToInclusive { #[cfg(feature = "alloc")] #[cfg_attr(doc, doc(cfg(feature = "alloc")))] -impl Encode for Rc { +impl Encode for Rc { + type Error = T::Error; + #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { T::encode(self, stream) } } -impl Encode for RefCell { +impl Encode for RefCell { + type Error = RefCellEncodeError; + #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { - let value = self - .try_borrow() - .map_err(EncodeError::BadBorrow)?; + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { + let value = self.try_borrow() + .map_err(RefCellEncodeError::Borrow)?; T::encode(&value, stream) + .map_err(RefCellEncodeError::Value)?; + + Ok(()) } } -impl Encode for core::result::Result { +impl Encode for core::result::Result +where + T: Encode, + E: Encode, +{ + type Error = Err; + /// Encodes a sign denoting the result's variant. /// This is `false` for `Ok` instances and `true` for `Err` instances. /// /// If `Ok`, then the contained value is encoded after this sign. #[inline] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { // The sign here is `false` for `Ok` objects and // `true` for `Err` objects. match *self { Ok(ref v) => { - false.encode(stream)?; + false.encode(stream).unwrap(); v.encode(stream)?; } Err(ref e) => { - true.encode(stream)?; + true.encode(stream).unwrap(); e.encode(stream)?; } }; @@ -556,9 +666,11 @@ impl Encode for core::result::Result { #[cfg(feature = "std")] #[cfg_attr(doc, doc(cfg(feature = "std")))] -impl Encode for RwLock { +impl Encode for RwLock { + type Error = T::Error; + #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { self .read() .or_else(|e| Ok(e.into_inner()))? @@ -567,17 +679,21 @@ impl Encode for RwLock { } impl Encode for Saturating { + type Error = T::Error; + #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { self.0.encode(stream) } } impl Encode for SocketAddr { + type Error = Infallible; + /// This implementation encoded as discriminant denoting the IP version of the address (i.e. `4` for IPv4 and `6` for IPv6). /// This is then followed by the respective address' own encoding (either [`SocketAddrV4`] or [`SocketAddrV6`]). #[inline] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { // The discriminant here is the IP version. match *self { @@ -597,9 +713,11 @@ impl Encode for SocketAddr { } impl Encode for SocketAddrV4 { + type Error = Infallible; + /// Encodes the address's bits followed by the port number, both of which in big-endian. #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { self.ip().encode(stream)?; self.port().encode(stream)?; @@ -608,9 +726,11 @@ impl Encode for SocketAddrV4 { } impl Encode for SocketAddrV6 { + type Error = Infallible; + /// Encodes the address's bits followed by the port number, flow information, and scope identifier -- all of which in big-endian. #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { self.ip().encode(stream)?; self.port().encode(stream)?; self.flowinfo().encode(stream)?; @@ -621,24 +741,23 @@ impl Encode for SocketAddrV6 { } impl Encode for str { + type Error = <[u8] as Encode>::Error; + /// Encodes the string identically to [a byte slice](slice) containing the string's byte values. #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { - // Optimised encode. Don't just rely on `[char]`. - - self.len().encode(stream)?; - stream.write(self.as_bytes()); - - Ok(()) + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { + self.as_bytes().encode(stream) } } #[cfg(feature = "alloc")] #[cfg_attr(doc, doc(cfg(feature = "alloc")))] impl Encode for String { + type Error = ::Error; + /// See [`str`]. #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { self.as_str().encode(stream) } } @@ -646,11 +765,13 @@ impl Encode for String { #[cfg(feature = "std")] #[cfg_attr(doc, doc(cfg(feature = "std")))] impl Encode for SystemTime { + type Error = Infallible; + /// Encodes the time point as the nearest, signed UNIX timestamp. /// /// Examples of some timestamps and their encodings include: /// - /// | ISO 8601 | UNIX / bZipper | + /// | ISO 8601 | UNIX / Librum | /// | :-------------------------- | -------------: | /// | `2024-11-03T12:02:01+01:00` | +1730631721 | /// | `1989-06-03T20:00:00+09:00` | +13258800 | @@ -658,7 +779,7 @@ impl Encode for SystemTime { /// | `1945-05-04T18:30:00+02:00` | -778231800 | #[expect(clippy::cast_possible_wrap)] #[inline] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { let time = if *self >= UNIX_EPOCH { let duration = self .duration_since(UNIX_EPOCH) @@ -673,51 +794,61 @@ impl Encode for SystemTime { 0x0 - duration.as_secs() as i64 }; - time.encode(stream) + time.encode(stream).unwrap(); + Ok(()) } } impl Encode for () { + type Error = Infallible; + #[inline(always)] - fn encode(&self, _stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, _stream: &mut OStream) -> Result<(), Self::Error> { Ok(()) } } impl Encode for usize { - /// Casts `self` to [`u16`] and encodes. - /// - /// If this conversion isn't possible for the given value, then the [`IsizeOutOfRange`](EncodeError::UsizeOutOfRange) error is returned. - #[inline] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { - let value = u16::try_from(*self) - .map_err(|_| EncodeError::UsizeOutOfRange(*self))?; + type Error = UsizeEncodeError; - value.encode(stream) + /// Casts `self` to [`u16`] and encodes the result. + #[inline] + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { + let value = u16::try_from(*self) + .map_err(|_| UsizeEncodeError(*self))?; + + value.encode(stream).unwrap(); + Ok(()) } } #[cfg(feature = "alloc")] #[cfg_attr(doc, doc(cfg(feature = "alloc")))] impl Encode for Vec { + type Error = <[T] as Encode>::Error; + #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { self.as_slice().encode(stream) } } impl Encode for Wrapping { + type Error = T::Error; + #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { self.0.encode(stream) } } macro_rules! impl_numeric { ($ty:ty$(,)?) => { - impl ::bzipper::Encode for $ty { + impl ::librum::Encode for $ty { + type Error = ::core::convert::Infallible; + #[inline] - fn encode(&self, stream: &mut OStream) -> ::core::result::Result<(), ::bzipper::error::EncodeError> { + fn encode(&self, stream: &mut OStream) -> ::core::result::Result<(), Self::Error> { stream.write(&self.to_be_bytes()); Ok(()) @@ -731,9 +862,13 @@ macro_rules! impl_tuple { $($captures:ident: $tys:ident),+$(,)? } => { #[doc(hidden)] - impl<$($tys: ::bzipper::Encode, )*> ::bzipper::Encode for ($($tys, )*) { + impl<$($tys, )* E> ::librum::Encode for ($($tys, )*) + where + $($tys: Encode, )* { + type Error = E; + #[inline(always)] - fn encode(&self, stream: &mut ::bzipper::OStream) -> ::core::result::Result<(), ::bzipper::error::EncodeError> { + fn encode(&self, stream: &mut ::librum::OStream) -> ::core::result::Result<(), Self::Error> { let ($(ref $captures, )*) = *self; $( @@ -748,9 +883,11 @@ macro_rules! impl_tuple { macro_rules! impl_non_zero { ($ty:ty$(,)?) => { - impl ::bzipper::Encode for ::core::num::NonZero<$ty> { + impl ::librum::Encode for ::core::num::NonZero<$ty> { + type Error = <$ty as ::librum::Encode>::Error; + #[inline(always)] - fn encode(&self, stream: &mut OStream) -> ::core::result::Result<(), ::bzipper::error::EncodeError> { + fn encode(&self, stream: &mut OStream) -> ::core::result::Result<(), Self::Error> { self.get().encode(stream) } } @@ -765,12 +902,14 @@ macro_rules! impl_atomic { } => { #[cfg(target_has_atomic = $width)] #[cfg_attr(doc, doc(cfg(target_has_atomic = $width)))] - impl ::bzipper::Encode for $atomic_ty { + impl ::librum::Encode for $atomic_ty { + type Error = <$ty as ::librum::Encode>::Error; + /// Encodes the atomic with the same scheme as that of the atomic type's primitive counterpart. /// /// The atomic object itself is read with the [`Relaxed`](core::sync::atomic::Ordering) ordering scheme. #[inline(always)] - fn encode(&self, stream: &mut ::bzipper::OStream) -> ::core::result::Result<(), ::bzipper::error::EncodeError> { + fn encode(&self, stream: &mut ::librum::OStream) -> ::core::result::Result<(), Self::Error> { self.load(::std::sync::atomic::Ordering::Relaxed).encode(stream) } } diff --git a/bzipper/src/encode/test.rs b/librum/src/encode/tests.rs similarity index 71% rename from bzipper/src/encode/test.rs rename to librum/src/encode/tests.rs index f88b238..04c71db 100644 --- a/bzipper/src/encode/test.rs +++ b/librum/src/encode/tests.rs @@ -1,6 +1,6 @@ // Copyright 2024 Gabriel Bjørnager Jensen. // -// This file is part of bzipper. +// This file is part of librum. //test!(you can redistribut => []); // it and/or modify it under the terms of the GNU // Lesser General Public License as published by @@ -8,52 +8,38 @@ // of the License, or (at your option) any later // version. // -// bzipper is distributed in the hope that it will +// librum 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 +// er General Public License along with librum. If // not, see . -use core::time::Duration; +use librum::{Encode, OStream, SizedEncode, SizedStr}; +use std::time::Duration; use std::time::{SystemTime, UNIX_EPOCH}; +use std::vec; +use std::vec::Vec; -use alloc::vec; -use alloc::vec::Vec; -use bzipper::{Encode, OStream, SizedEncode, SizedStr}; +macro_rules! test { + ($ty:ty: $value:expr => $data:expr) => {{ + let data = $data; -#[derive(SizedEncode)] -struct Foo(char); + let mut buf = vec![0x00; data.len()]; -#[derive(SizedEncode)] -#[repr(u8)] // Not honoured. -enum Bar { - Unit = 0x45, + let mut stream = OStream::new(&mut buf); + <$ty as Encode>::encode(&$value, &mut stream).unwrap(); - Pretty(bool) = 127, - - Teacher { initials: [char; 0x3] }, + let len = stream.close(); + assert_eq!(&buf[..len], data.as_slice()); + }}; } #[test] fn test_encode() { - macro_rules! test { - ($ty:ty: $value:expr => $data:expr) => {{ - let data = $data; - - let mut buf = vec![0x00; data.len()]; - - let mut stream = OStream::new(&mut buf); - <$ty as Encode>::encode(&$value, &mut stream).unwrap(); - - let len = stream.close(); - assert_eq!(&buf[..len], data.as_slice()); - }}; - } - test!(u8: 0x00 => [0x00]); test!(u8: 0xFF => [0xFF]); test!(u8: 0x7F => [0x7F]); @@ -88,17 +74,6 @@ fn test_encode() { test!(Option<()>: None => [0x00]); test!(Option<()>: Some(()) => [0x01]); - test!(Foo: Foo('\u{FDF2}') => [0x00, 0x00, 0xFD, 0xF2]); - - test!(Bar: Bar::Unit => [0x00, 0x45]); - - test!(Bar: Bar::Pretty(true) => [0x00, 0x7F, 0x01]); - - test!(Bar: Bar::Teacher { initials: ['T', 'L', '\0'] } => [ - 0x00, 0x80, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, - 0x00, 0x4C, 0x00, 0x00, 0x00, 0x00, - ]); - test!(Vec: From::from([0x8000, 0x8000, 0x8000]) => [0x00, 0x03, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00]); test!(SystemTime: UNIX_EPOCH => [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); @@ -107,3 +82,31 @@ fn test_encode() { test!(SystemTime: UNIX_EPOCH + Duration::from_secs(0x1) => [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]); } + +#[test] +fn test_encode_derive() { + #[derive(Encode, SizedEncode)] + #[repr(transparent)] + struct Foo(char); + + #[derive(Encode, SizedEncode)] + #[repr(u8)] + enum Bar { + Unit = 0x45, + + Pretty(bool) = 127, + + Teacher { initials: [char; 0x3] }, + } + + test!(Foo: Foo('\u{FDF2}') => [0x00, 0x00, 0xFD, 0xF2]); + + test!(Bar: Bar::Unit => [0x45]); + + test!(Bar: Bar::Pretty(true) => [0x7F, 0x01]); + + test!(Bar: Bar::Teacher { initials: ['T', 'L', '\0'] } => [ + 0x80, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, + 0x4C, 0x00, 0x00, 0x00, 0x00, + ]); +} diff --git a/librum/src/error/bool_decode_error.rs b/librum/src/error/bool_decode_error.rs new file mode 100644 index 0000000..3cbeab0 --- /dev/null +++ b/librum/src/error/bool_decode_error.rs @@ -0,0 +1,43 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Librum. +// +// Librum 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. +// +// Librum 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 Librum. If +// not, see . + +use core::error::Error; +use core::fmt::{self, Display, Formatter}; + +/// A boolean could not be decoded. +/// +/// The encoding scheme for [`bool`] only defines the 8-bit values `0` and `1` (as `false` and `true`, respectively). +/// If any other 8-bit is read by <bool as [Decode](crate::Decode)>::[decode](crate::Decode::decode), then an instance of this type is returned. +#[derive(Debug)] +#[must_use] +pub struct BoolDecodeError { + /// The invalid value. + pub value: u8, +} + +impl Display for BoolDecodeError { + #[inline(always)] + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "expected boolean but got `{:#02X}`", self.value) + } +} + +impl Error for BoolDecodeError { } diff --git a/librum/src/error/c_string_decode_error.rs b/librum/src/error/c_string_decode_error.rs new file mode 100644 index 0000000..8bf8b91 --- /dev/null +++ b/librum/src/error/c_string_decode_error.rs @@ -0,0 +1,46 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Librum. +// +// Librum 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. +// +// Librum 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 Librum. If +// not, see . + +use core::error::Error; +use core::fmt::{self, Display, Formatter}; + +/// A C-like string could not be decoded. +/// +/// This error is generatead when <[CString](alloc::ffi::CString) as [Decode](crate::Decode)>::[decode](crate::Decode::decode) encounteres a null byte within bounds. +/// +/// Note that although any null value is *theoretically* also the string's null terminator, the implementations for [`CStr`](core::ffi::CStr) and `CString` use the same encoding scheme as [`[u8]`](slice). +/// This is mainly for efficiency's sake (as to allow the entire stream to be read at once), but this also allows for the aforementioned case to happen. +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +#[derive(Debug)] +#[must_use] +pub struct CStringDecodeError { + /// The index of the null value. + pub index: usize, +} + +impl Display for CStringDecodeError { + #[inline(always)] + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "expected c string but found null value within bounds at '{}`", self.index) + } +} + +impl Error for CStringDecodeError { } diff --git a/librum/src/error/char_decode_error.rs b/librum/src/error/char_decode_error.rs new file mode 100644 index 0000000..8b9a091 --- /dev/null +++ b/librum/src/error/char_decode_error.rs @@ -0,0 +1,46 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Librum. +// +// Librum 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. +// +// Librum 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 Librum. If +// not, see . + +use core::error::Error; +use core::fmt::{self, Display, Formatter}; + +/// A character could not be decoded. +/// +/// Unicode defines only the code points inclusively between `U+0000` and `U+D7FFF` as well between `U+E0000` and `U+10FFFF` as being valid. +/// UTF-32 (the format used by the [`char`] data type) additionally specifies that these code points are padded to 32 bits. +/// +/// The encoding scheme used by `char` yields an untransformed representation (disregarding endian corrections), but this regrettably also leads to many bit patterns being undefined with respect to UTF-32. +/// If any of these values is read by <char as [Decode](crate::Decode)>::[decode](crate::Decode::decode), then an instance of this error type is returned. +#[derive(Debug)] +#[must_use] +pub struct CharDecodeError { + /// The undefined code point. + pub code_point: u32, +} + +impl Display for CharDecodeError { + #[inline(always)] + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "code point U+{:04X} is not defined", self.code_point) + } +} + +impl Error for CharDecodeError { } diff --git a/librum/src/error/collection_decode_error.rs b/librum/src/error/collection_decode_error.rs new file mode 100644 index 0000000..9e829a8 --- /dev/null +++ b/librum/src/error/collection_decode_error.rs @@ -0,0 +1,96 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Librum. +// +// Librum 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. +// +// Librum 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 Librum. If +// not, see . + +use core::convert::Infallible; +use core::error::Error; +use core::fmt::{self, Display, Formatter}; + +/// A collection could not be decoded. +/// +/// This type is intended as a partially-generic decode error for collections. +/// It supports denoting an error for when the collection's length is invalid -- see the [`Length`](Self::Length) variant -- and when an element is invalid -- see the [`Item`](Self::Item)) variant. +/// +/// The most common form of this type is CollectionDecodeError<[Infallible](core::convert::Infallible), [ItemDecodeError](crate::error::ItemDecodeError)<[usize], ..>, but this may not always necessarily be the preferred form. +/// +/// An example of a type using a different form is [`SizedStr`](crate::SizedStr), which uses CollectionDecodeError<[`SizeError`](crate::error::SizeError), [Utf8Error](crate::error::Utf8Error)>. +#[derive(Debug)] +#[must_use] +pub enum CollectionDecodeError { + /// The collection length could not be decoded or was invalid. + /// + /// For most dynamically-sized collections, the suitable type here is [`Infallible`] due to there basically being no restriction on the collection's size (depending on the data type used for denoting lengths). + /// + /// Sometimes the length isn't even encoded in the stream (instead lying in the type signature), and in these cases the appropriate type would also be `Infallible`. + Length(L), + + /// A collection item could not be decoded. + /// + /// Sometimes the index of the item may be desired. + /// In these cases the [`ItemDecodeError`](crate::error::ItemDecodeError) could be used here. + Item(I), +} + +impl Display for CollectionDecodeError +where + L: Display, + I: Display, +{ + #[inline] + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + use CollectionDecodeError::*; + + match *self { + Length(ref e) + => write!(f, "unable to decode collection length: {e}"), + + Item(ref e) + => write!(f, "unable to decode collection item: {e}"), + } + } +} + +impl Error for CollectionDecodeError +where + L: Error + 'static, + I: Error + 'static, +{ + #[inline] + fn source(&self) -> Option<&(dyn Error + 'static)> { + use CollectionDecodeError::*; + + match *self { + Length(ref e) => Some(e), + + Item(ref e) => Some(e), + } + } +} + +impl From> for Infallible +where + L: Into, + I: Into, +{ + #[inline(always)] + fn from(_value: CollectionDecodeError) -> Self { + unreachable!() + } +} diff --git a/librum/src/error/collection_encode_error.rs b/librum/src/error/collection_encode_error.rs new file mode 100644 index 0000000..0f64a85 --- /dev/null +++ b/librum/src/error/collection_encode_error.rs @@ -0,0 +1,86 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Librum. +// +// Librum 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. +// +// Librum 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 Librum. If +// not, see . + +use core::convert::Infallible; +use core::error::Error; +use core::fmt::{self, Display, Formatter}; + +/// A collection could not be encoded. +/// +/// This type is intended as a partially-generic encode error for collections. +/// It supports denoting an error for when the collection's length is invalid -- see the [`Length`](Self::Length) variant -- and when an element is invalid -- see the [`Item`](Self::Item)) variant. +#[derive(Debug)] +#[must_use] +pub enum CollectionEncodeError { + /// The collection length could not be encoded. + Length(L), + + /// A collection item could not be encoded. + Item(I), +} + +impl Display for CollectionEncodeError +where + L: Display, + I: Display, +{ + #[inline] + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + use CollectionEncodeError::*; + + match *self { + Length(ref e) + => write!(f, "unable to encode collection length: {e}"), + + Item(ref e) + => write!(f, "unable to encode collection item: {e}"), + } + } +} + +impl Error for CollectionEncodeError +where + L: Error + 'static, + I: Error + 'static, +{ + #[inline] + fn source(&self) -> Option<&(dyn Error + 'static)> { + use CollectionEncodeError::*; + + match *self { + Length(ref e) => Some(e), + + Item(ref e) => Some(e), + } + } +} + +impl From> for Infallible +where + L: Into, + I: Into, +{ + #[inline(always)] + fn from(_value: CollectionEncodeError) -> Self { + unreachable!() + } +} + diff --git a/librum/src/error/enum_decode_error.rs b/librum/src/error/enum_decode_error.rs new file mode 100644 index 0000000..933e840 --- /dev/null +++ b/librum/src/error/enum_decode_error.rs @@ -0,0 +1,98 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Librum. +// +// Librum 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. +// +// Librum 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 Librum. If +// not, see . + +use crate::Decode; + +use core::convert::Infallible; +use core::error::Error; +use core::fmt::{self, Debug, Display, Formatter}; + +/// An invalid enumeration descriminant was provided. +#[derive(Debug)] +#[must_use] +pub enum EnumDecodeError { + /// The discriminant could not be decoded. + InvalidDiscriminant(D::Error), + + /// An otherwise valid discriminant has not been assigned. + /// + /// Remember that this error does **not** indicate that the discriminant couldn't be decoded, merely that it does not match with that of any variant. + /// See also [`InvalidDiscriminant`](Self::InvalidDiscriminant). + UnassignedDiscriminant { + /// The unassigned discriminant value. + value: D + }, + + /// A field could not be encoded. + Field(F), +} + +impl Display for EnumDecodeError +where + D: Decode + Display, + F: Display, +{ + #[inline] + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + use EnumDecodeError::*; + + match *self { + InvalidDiscriminant(ref e) + => write!(f, "discriminant could not be decoded: {e}"), + + UnassignedDiscriminant { ref value } + => write!(f, "`{value}` is not an assigned discriminant for the given enumeration"), + + Field(ref e) + => write!(f, "variant could not be decoded: {e}"), + } + } +} + +impl Error for EnumDecodeError +where + D: Debug + Decode + Display, + F: Error + 'static, +{ + #[inline] + fn source(&self) -> Option<&(dyn Error + 'static)> { + use EnumDecodeError::*; + + match *self { + InvalidDiscriminant(ref e) => Some(e), + + Field(ref e) => Some(e), + + _ => None, + } + } +} + +impl From> for Infallible +where + D: Decode>, + F: Into, +{ + #[inline(always)] + fn from(_value: EnumDecodeError) -> Self { + unreachable!() + } +} diff --git a/librum/src/error/enum_encode_error.rs b/librum/src/error/enum_encode_error.rs new file mode 100644 index 0000000..e9935a5 --- /dev/null +++ b/librum/src/error/enum_encode_error.rs @@ -0,0 +1,84 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Librum. +// +// Librum 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. +// +// Librum 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 Librum. If +// not, see . + +use crate::Encode; + +use core::convert::Infallible; +use core::error::Error; +use core::fmt::{self, Debug, Display, Formatter}; + +/// An invalid enumeration descriminant was provided. +#[derive(Debug)] +#[must_use] +pub enum EnumEncodeError { + /// The discriminant could not be encoded. + Discriminant(D::Error), + + /// A field could not be encoded. + Field(F), +} + +impl Display for EnumEncodeError +where + D: Display + Encode, + F: Display, +{ + #[inline] + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + use EnumEncodeError::*; + + match *self { + Discriminant(ref e) + => write!(f, "discriminant could not be encoded: {e}"), + + Field(ref e) + => write!(f, "field could not be encoded: {e}"), + } + } +} + +impl Error for EnumEncodeError +where + D: Debug + Display + Encode, + F: Error + 'static, +{ + #[inline] + fn source(&self) -> Option<&(dyn Error + 'static)> { + use EnumEncodeError::*; + + match *self { + Discriminant(ref e) => Some(e), + + Field(ref e) => Some(e), + } + } +} + +impl From> for Infallible +where + D: Encode>, + F: Into, +{ + #[inline(always)] + fn from(_value: EnumEncodeError) -> Self { + unreachable!() + } +} diff --git a/librum/src/error/generic_decode_error.rs b/librum/src/error/generic_decode_error.rs new file mode 100644 index 0000000..bc28425 --- /dev/null +++ b/librum/src/error/generic_decode_error.rs @@ -0,0 +1,229 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Librum. +// +// Librum 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. +// +// Librum 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 Librum. If +// not, see . + +use crate::{Decode, PrimitiveDiscriminant}; +use crate::error::{ + BoolDecodeError, + CollectionDecodeError, + CStringDecodeError, + EnumDecodeError, + ItemDecodeError, + NonZeroDecodeError, + SizeError, + Utf8Error, + SystemTimeDecodeError, +}; + +use core::convert::Infallible; +use core::error::Error; +use core::fmt::{self, Display, Formatter}; +use core::hint::unreachable_unchecked; + +/// A decoding failed. +/// +/// The intended use of this type is by [derived](derive@Decode) implementations of [`Decode`]. +/// Manual implementors are recommended to use a custom or less generic type for the sake of efficiency. +#[derive(Debug)] +#[must_use] +#[non_exhaustive] +pub enum GenericDecodeError { + /// A string contained a non-UTF-8 sequence. + BadString(Utf8Error), + + /// A boolean was neither `false` nor `true`. + InvalidBool(BoolDecodeError), + + /// A C-like string contained a null byte. + #[cfg(feature = "std")] + #[cfg_attr(doc, doc(cfg(feature = "std")))] + NullString(CStringDecodeError), + + /// A non-null integer was null. + NullInteger(NonZeroDecodeError), + + /// A statically-sized buffer was too small. + SmallBuffer(SizeError), + + /// An unassigned discriminant value was encountered. + /// + /// The contained value denotes the raw, numerical value of the discriminant. + UnassignedDiscriminant { + /// The raw value of the discriminant. + value: u128 + }, + + /// The [`SystemTime`](std::time::SystemTime) type was too narrow. + #[cfg(feature = "std")] + #[cfg_attr(doc, doc(cfg(feature = "std")))] + NarrowSystemTime(SystemTimeDecodeError), +} + +impl Display for GenericDecodeError { + #[inline] + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + use GenericDecodeError::*; + + match *self { + BadString(ref e) + => write!(f, "{e}"), + + InvalidBool(ref e) + => write!(f, "{e}"), + + NullString(ref e) + => write!(f, "{e}"), + + NullInteger(ref e) + => write!(f, "{e}"), + + SmallBuffer(ref e) + => write!(f, "{e}"), + + UnassignedDiscriminant { value } + => write!(f, "discriminant value `{value:#X} has not been assigned"), + + #[cfg(feature = "std")] + NarrowSystemTime(ref e) + => write!(f, "{e}"), + } + } +} + +impl Error for GenericDecodeError { + #[inline] + fn source(&self) -> Option<&(dyn Error + 'static)> { + use GenericDecodeError::*; + + match *self { + BadString(ref e) => Some(e), + + InvalidBool(ref e) => Some(e), + + #[cfg(feature = "std")] + NullString(ref e) => Some(e), + + NullInteger(ref e) => Some(e), + + SmallBuffer(ref e) => Some(e), + + #[cfg(feature = "std")] + NarrowSystemTime(ref e) => Some(e), + + _ => None, + } + } +} + +impl From for GenericDecodeError { + #[inline(always)] + fn from(value: BoolDecodeError) -> Self { + Self::InvalidBool(value) + } +} + +impl From> for GenericDecodeError +where + L: Into, + I: Into, +{ + #[inline(always)] + fn from(value: CollectionDecodeError) -> Self { + use CollectionDecodeError::*; + + match value { + Length(e) => e.into(), + + Item(e) => e.into(), + } + } +} + +impl From> for GenericDecodeError +where + D: Decode> + PrimitiveDiscriminant, + F: Into, +{ + #[inline(always)] + fn from(value: EnumDecodeError) -> Self { + use EnumDecodeError::*; + + match value { + InvalidDiscriminant(e) => e.into(), + + UnassignedDiscriminant { value } => Self::UnassignedDiscriminant { value: value.to_u128() }, + + Field(e) => e.into(), + } + } +} + +impl From for GenericDecodeError { + #[inline(always)] + fn from(_value: Infallible) -> Self { + unsafe { unreachable_unchecked() } + } +} + +impl> From> for GenericDecodeError { + #[inline(always)] + fn from(value: ItemDecodeError) -> Self { + value.error.into() + } +} + +#[cfg(feature = "std")] +#[cfg_attr(doc, doc(cfg(feature = "std")))] +impl From for GenericDecodeError { + #[inline(always)] + fn from(value: CStringDecodeError) -> Self { + Self::NullString(value) + } +} + +impl From for GenericDecodeError { + #[inline(always)] + fn from(value: NonZeroDecodeError) -> Self { + Self::NullInteger(value) + } +} + +impl From for GenericDecodeError { + #[inline(always)] + fn from(value: SizeError) -> Self { + Self::SmallBuffer(value) + } +} + +#[cfg(feature = "std")] +#[cfg_attr(doc, doc(cfg(feature = "std")))] +impl From for GenericDecodeError { + #[inline(always)] + fn from(value: SystemTimeDecodeError) -> Self { + Self::NarrowSystemTime(value) + } +} + +impl From for GenericDecodeError { + #[inline(always)] + fn from(value: Utf8Error) -> Self { + Self::BadString(value) + } +} diff --git a/librum/src/error/generic_encode_error.rs b/librum/src/error/generic_encode_error.rs new file mode 100644 index 0000000..2c24cbb --- /dev/null +++ b/librum/src/error/generic_encode_error.rs @@ -0,0 +1,154 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Librum. +// +// Librum 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. +// +// Librum 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 Librum. If +// not, see . + +use crate::Encode; +use crate::error::{ + CollectionEncodeError, + EnumEncodeError, + IsizeEncodeError, + ItemEncodeError, + UsizeEncodeError, +}; + +use core::cell::BorrowError; +use core::convert::Infallible; +use core::error::Error; +use core::fmt::{self, Display, Formatter}; +use core::hint::unreachable_unchecked; + +/// A decoding failed. +/// +/// The intended use of this type is by [derived](derive@crate::Encode) implementations of [`Encode`](crate::Encode). +/// Manual implementors are recommended to use a custom or less generic type for the sake of efficiency. +#[derive(Debug)] +#[must_use] +#[non_exhaustive] +pub enum GenericEncodeError { + /// A [`RefCell`](core::cell::RefCell) object could not be borrowed. + BadBorrow(BorrowError), + + /// An `isize` object was outside the allowed domain. + LargeIsize(IsizeEncodeError), + + /// A `usize` object was outside the allowed domain. + LargeUsize(UsizeEncodeError), +} + +impl Display for GenericEncodeError { + #[inline] + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + use GenericEncodeError::*; + + let e: &dyn Display = match *self { + BadBorrow(ref e) => e, + + LargeIsize(ref e) => e, + + LargeUsize(ref e) => e, + }; + + e.fmt(f) + } +} + +impl Error for GenericEncodeError { + #[inline] + fn source(&self) -> Option<&(dyn Error + 'static)> { + use GenericEncodeError::*; + + match *self { + BadBorrow(ref e) => Some(e), + + LargeIsize(ref e) => Some(e), + + LargeUsize(ref e) => Some(e), + } + } +} + +impl From for GenericEncodeError { + #[inline(always)] + fn from(value: BorrowError) -> Self { + Self::BadBorrow(value) + } +} + +impl From> for GenericEncodeError +where + L: Into, + I: Into, +{ + #[inline(always)] + fn from(value: CollectionEncodeError) -> Self { + use CollectionEncodeError::*; + + match value { + Length(e) => e.into(), + + Item(e) => e.into(), + } + } +} + +impl From> for GenericEncodeError +where + D: Encode>, + F: Into, +{ + #[inline(always)] + fn from(value: EnumEncodeError) -> Self { + use EnumEncodeError::*; + + match value { + Discriminant(e) => e.into(), + + Field(e) => e.into(), + } + } +} + +impl From for GenericEncodeError { + #[inline(always)] + fn from(_value: Infallible) -> Self { + unsafe { unreachable_unchecked() } + } +} + +impl From for GenericEncodeError { + #[inline(always)] + fn from(value: IsizeEncodeError) -> Self { + Self::LargeIsize(value) + } +} + +impl> From> for GenericEncodeError { + #[inline(always)] + fn from(value: ItemEncodeError) -> Self { + value.error.into() + } +} + +impl From for GenericEncodeError { + #[inline(always)] + fn from(value: UsizeEncodeError) -> Self { + Self::LargeUsize(value) + } +} diff --git a/librum/src/error/isize_encode_error.rs b/librum/src/error/isize_encode_error.rs new file mode 100644 index 0000000..1915ab1 --- /dev/null +++ b/librum/src/error/isize_encode_error.rs @@ -0,0 +1,42 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Librum. +// +// Librum 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. +// +// Librum 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 Librum. If +// not, see . + +use core::error::Error; +use core::fmt::{self, Display, Formatter}; + +/// An [`isize`] value could not be decoded. +/// +/// Any `isize` object that can fit in an [`i16`] can be encoded successfully. +#[derive(Debug)] +#[must_use] +pub struct IsizeEncodeError( + /// The unencodable value. + pub isize, +); + +impl Display for IsizeEncodeError { + #[inline(always)] + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "signed size value ({}) cannot be serialised: must be in the range ({}) to ({})", self.0, i16::MIN, i16::MAX) + } +} + +impl Error for IsizeEncodeError { } diff --git a/librum/src/error/item_decode_error.rs b/librum/src/error/item_decode_error.rs new file mode 100644 index 0000000..59ef30d --- /dev/null +++ b/librum/src/error/item_decode_error.rs @@ -0,0 +1,66 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Librum. +// +// Librum 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. +// +// Librum 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 Librum. If +// not, see . + +use core::convert::Infallible; +use core::error::Error; +use core::fmt::{self, Debug, Display, Formatter}; + +/// A collection's item could not be decoded. +/// +/// See also [`CollectionDecodeError`](crate::error::CollectionDecodeError). +#[derive(Debug)] +#[must_use] +pub struct ItemDecodeError { + /// The index of the invalid item. + pub index: I, + + /// The decoder's error. + pub error: E, +} + +impl Display for ItemDecodeError +where + I: Display, + E: Display, +{ + #[inline(always)] + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "could not decode item at `{}`: {}", self.index, self.error) + } +} + +impl Error for ItemDecodeError +where + Self: Debug + Display, + E: Error + 'static, +{ + #[inline(always)] + fn source(&self) -> Option<&(dyn Error + 'static)> { + Some(&self.error) + } +} + +impl From> for Infallible { + #[inline(always)] + fn from(_value: ItemDecodeError) -> Self { + unreachable!() + } +} diff --git a/librum/src/error/item_encode_error.rs b/librum/src/error/item_encode_error.rs new file mode 100644 index 0000000..0c482aa --- /dev/null +++ b/librum/src/error/item_encode_error.rs @@ -0,0 +1,66 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Librum. +// +// Librum 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. +// +// Librum 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 Librum. If +// not, see . + +use core::convert::Infallible; +use core::error::Error; +use core::fmt::{self, Debug, Display, Formatter}; + +/// A collection's item could not be encoded. +/// +/// See also [`CollectionEncodeError`](crate::error::CollectionEncodeError). +#[derive(Debug)] +#[must_use] +pub struct ItemEncodeError { + /// The index of the invalid item. + pub index: I, + + /// The encoder's error. + pub error: E, +} + +impl Display for ItemEncodeError +where + I: Display, + E: Display, +{ + #[inline(always)] + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "could not encode item at `{}`: {}", self.index, self.error) + } +} + +impl Error for ItemEncodeError +where + Self: Debug + Display, + E: Error + 'static, +{ + #[inline(always)] + fn source(&self) -> Option<&(dyn Error + 'static)> { + Some(&self.error) + } +} + +impl> From> for Infallible { + #[inline(always)] + fn from(_value: ItemEncodeError) -> Self { + unreachable!() + } +} diff --git a/librum/src/error/mod.rs b/librum/src/error/mod.rs new file mode 100644 index 0000000..9bb512c --- /dev/null +++ b/librum/src/error/mod.rs @@ -0,0 +1,52 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Librum. +// +// Librum 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. +// +// Librum 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 Librum. If +// not, see . + +//! Error variants. +//! +//! This module defines the error types used by Librum. +//! All of these types define (at least conditionally) the [`Error`](core::error::Error) trait. + +use crate::use_mod; + +use_mod!(pub bool_decode_error); +use_mod!(pub char_decode_error); +use_mod!(pub collection_decode_error); +use_mod!(pub collection_encode_error); +use_mod!(pub enum_encode_error); +use_mod!(pub enum_decode_error); +use_mod!(pub generic_decode_error); +use_mod!(pub generic_encode_error); +use_mod!(pub isize_encode_error); +use_mod!(pub item_decode_error); +use_mod!(pub item_encode_error); +use_mod!(pub non_zero_decode_error); +use_mod!(pub ref_cell_encode_error); +use_mod!(pub size_error); +use_mod!(pub string_error); +use_mod!(pub usize_encode_error); +use_mod!(pub utf16_error); +use_mod!(pub utf8_error); + +#[cfg(feature = "alloc")] +use_mod!(pub c_string_decode_error); + +#[cfg(feature = "std")] +use_mod!(pub system_time_decode_error); diff --git a/librum/src/error/non_zero_decode_error.rs b/librum/src/error/non_zero_decode_error.rs new file mode 100644 index 0000000..92ef77b --- /dev/null +++ b/librum/src/error/non_zero_decode_error.rs @@ -0,0 +1,38 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Librum. +// +// Librum 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. +// +// Librum 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 Librum. If +// not, see . + +use core::error::Error; +use core::fmt::{self, Display, Formatter}; + +/// A non-zero integer could not be decoded. +/// +/// The implementations of [`Decode`](crate::Decode) for [NonZero](core::num::NonZero)<T> yield this error type if decoding `T` yields zero. +#[derive(Debug)] +pub struct NonZeroDecodeError; + +impl Display for NonZeroDecodeError { + #[inline(always)] + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "expected non-zero integer but found `0`") + } +} + +impl Error for NonZeroDecodeError { } diff --git a/librum/src/error/ref_cell_encode_error.rs b/librum/src/error/ref_cell_encode_error.rs new file mode 100644 index 0000000..bc8c699 --- /dev/null +++ b/librum/src/error/ref_cell_encode_error.rs @@ -0,0 +1,67 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Librum. +// +// Librum 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. +// +// Librum 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 Librum. If +// not, see . + +use core::cell::BorrowError; +use core::error::Error; +use core::fmt::{self, Display, Formatter}; + +/// A reference cell could not be encoded. +/// +/// The implementation of <[RefCell](core::cell::RefCell)<T> as [Encode](crate::Encode)>::[encode](crate::Encode::encode) will first attempt to call RefCell::[borrow](core::cell::RefCell::borrow). +/// If this call fails, then the returned error is again returned as a [`Borrow`](Self::Borrow) instance. +/// If the following call to T::encode fails instead, then the error returned from that call is passed on as a [`Value`](Self::Value) instance. +#[derive(Debug)] +#[must_use] +pub enum RefCellEncodeError { + /// The reference cell could not be borrowed. + Borrow(BorrowError), + + /// The contained value could not be encoded. + Value(E), +} + +impl Display for RefCellEncodeError { + #[inline] + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + use RefCellEncodeError::*; + + let e: &dyn Display = match *self { + Borrow(ref e) => e, + + Value(ref e) => e, + }; + + write!(f, "unable to encode reference cell: {e}") + } +} + +impl Error for RefCellEncodeError { + #[inline(always)] + fn source(&self) -> Option<&(dyn Error + 'static)> { + use RefCellEncodeError::*; + + match *self { + Borrow(ref e) => Some(e), + + Value(ref e) => Some(e) + } + } +} diff --git a/bzipper/src/error/size_error/mod.rs b/librum/src/error/size_error.rs similarity index 54% rename from bzipper/src/error/size_error/mod.rs rename to librum/src/error/size_error.rs index 8fb39f3..792d1d8 100644 --- a/bzipper/src/error/size_error/mod.rs +++ b/librum/src/error/size_error.rs @@ -1,41 +1,48 @@ // Copyright 2024 Gabriel Bjørnager Jensen. // -// This file is part of bZipper. +// This file is part of Librum. // -// bZipper is free software: you can redistribute -// it and/or modify it under the terms of the GNU +// Librum 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 +// Librum 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 +// er General Public License along with Librum. If // not, see . use core::error::Error; use core::fmt::{self, Display, Formatter}; /// A fixed-size buffer was too small. +/// +/// Some data types use a statically-sized buffer whilst still allowing for partial usage of this buffer (e.g. [`SizedSlice`](crate::SizedSlice)). +/// +/// Taking `SizedSlice` as an example, it encodes its actual length before encoding each of its elements. +/// It is allowed for any smaller-sized `SizedSlice` instance to decode a larger-sized encoding **if** the actual length still fits. +/// If not, then this error type is used to denote the error state. #[derive(Debug)] +#[must_use] pub struct SizeError { - /// The required amount of bytes. - pub req: usize, - /// The total capacity of the buffer. + pub cap: usize, + + /// The required amount of elements. pub len: usize, } impl Display for SizeError { #[inline(always)] fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "collection of size ({}) cannot hold ({}) elements", self.len, self.req) + write!(f, "collection of size ({}) cannot hold ({}) elements", self.cap, self.len) } } diff --git a/bzipper/src/error/string_error/mod.rs b/librum/src/error/string_error.rs similarity index 68% rename from bzipper/src/error/string_error/mod.rs rename to librum/src/error/string_error.rs index c39796f..f783f7a 100644 --- a/bzipper/src/error/string_error/mod.rs +++ b/librum/src/error/string_error.rs @@ -1,22 +1,22 @@ // Copyright 2024 Gabriel Bjørnager Jensen. // -// This file is part of bZipper. +// This file is part of Librum. // -// bZipper is free software: you can redistribute -// it and/or modify it under the terms of the GNU +// Librum 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 +// Librum 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 +// er General Public License along with Librum. If // not, see . use crate::error::{SizeError, Utf16Error, Utf8Error}; @@ -27,6 +27,7 @@ use core::fmt::{self, Display, Formatter}; /// String error variants. #[derive(Debug)] #[non_exhaustive] +#[must_use] pub enum StringError { /// An invalid UTF-16 sequence was encountered. BadUtf16(Utf16Error), @@ -44,14 +45,14 @@ impl Display for StringError { use StringError::*; match *self { - BadUtf16(ref source) - => write!(f, "bad utf-16: {source}"), + BadUtf16(ref e) + => write!(f, "bad utf-16: {e}"), - BadUtf8(ref source) - => write!(f, "bad utf-8: {source}"), + BadUtf8(ref e) + => write!(f, "bad utf-8: {e}"), - SmallBuffer(ref source) - => write!(f, "buffer too small: {source}"), + SmallBuffer(ref e) + => write!(f, "buffer too small: {e}"), } } } @@ -62,14 +63,11 @@ impl Error for StringError { use StringError::*; match *self { - BadUtf16(ref source) - => Some(source), + BadUtf16(ref e) => Some(e), - BadUtf8(ref source) - => Some(source), + BadUtf8(ref e) => Some(e), - SmallBuffer(ref source) - => Some(source), + SmallBuffer(ref e) => Some(e), } } } diff --git a/librum/src/error/system_time_decode_error.rs b/librum/src/error/system_time_decode_error.rs new file mode 100644 index 0000000..1b390f2 --- /dev/null +++ b/librum/src/error/system_time_decode_error.rs @@ -0,0 +1,46 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Librum. +// +// Librum 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. +// +// Librum 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 Librum. If +// not, see . + +use core::error::Error; +use core::fmt::{self, Display, Formatter}; + +/// The [`SystemTime`](std::time::SystemTime) type could not represent a UNIX timestamp. +/// +/// Note that a UNIX timestamp is here defined as a signed, 64-bit integer denoting a difference of time to 1 january 1970, as measured in Greenwich using seconds. +/// This error should therefore not occur on systems that use the same or a more precise counter. +#[cfg_attr(doc, doc(cfg(feature = "std")))] +#[derive(Debug)] +#[must_use] +pub struct SystemTimeDecodeError { + /// The unrepresentable timestamp. + pub timestamp: i64, +} + +#[cfg_attr(doc, doc(cfg(feature = "std")))] +impl Display for SystemTimeDecodeError { + #[inline(always)] + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "could not represent `{}` as a system timestamp", self.timestamp) + } +} + +#[cfg_attr(doc, doc(cfg(feature = "std")))] +impl Error for SystemTimeDecodeError { } diff --git a/librum/src/error/usize_encode_error.rs b/librum/src/error/usize_encode_error.rs new file mode 100644 index 0000000..ad0e6a5 --- /dev/null +++ b/librum/src/error/usize_encode_error.rs @@ -0,0 +1,42 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Librum. +// +// Librum 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. +// +// Librum 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 Librum. If +// not, see . + +use core::error::Error; +use core::fmt::{self, Display, Formatter}; + +/// A [`usize`] value could not be decoded. +/// +/// Any `usize` object that can fit in an [`u16`] can be encoded successfully. +#[derive(Debug)] +#[must_use] +pub struct UsizeEncodeError( + /// The unencodable value. + pub usize, +); + +impl Display for UsizeEncodeError { + #[inline(always)] + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "unsigned size value ({}) cannot be serialised: must be at most ({})", self.0, u16::MAX) + } +} + +impl Error for UsizeEncodeError { } diff --git a/bzipper/src/error/utf16_error/mod.rs b/librum/src/error/utf16_error.rs similarity index 80% rename from bzipper/src/error/utf16_error/mod.rs rename to librum/src/error/utf16_error.rs index 0279662..e2ee2d9 100644 --- a/bzipper/src/error/utf16_error/mod.rs +++ b/librum/src/error/utf16_error.rs @@ -1,22 +1,22 @@ // Copyright 2024 Gabriel Bjørnager Jensen. // -// This file is part of bZipper. +// This file is part of Librum. // -// bZipper is free software: you can redistribute -// it and/or modify it under the terms of the GNU +// Librum 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 +// Librum 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 +// er General Public License along with Librum. If // not, see . use core::error::Error; @@ -24,6 +24,7 @@ use core::fmt::{self, Display, Formatter}; /// An invalid UTF-16 sequence was encountered. #[derive(Debug)] +#[must_use] pub struct Utf16Error { /// The invalid UTF-16 hextet. pub value: u16, diff --git a/bzipper/src/error/utf8_error/mod.rs b/librum/src/error/utf8_error.rs similarity index 79% rename from bzipper/src/error/utf8_error/mod.rs rename to librum/src/error/utf8_error.rs index f3dc3e1..1d95df4 100644 --- a/bzipper/src/error/utf8_error/mod.rs +++ b/librum/src/error/utf8_error.rs @@ -1,22 +1,22 @@ // Copyright 2024 Gabriel Bjørnager Jensen. // -// This file is part of bZipper. +// This file is part of Librum. // -// bZipper is free software: you can redistribute -// it and/or modify it under the terms of the GNU +// Librum 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 +// Librum 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 +// er General Public License along with Librum. If // not, see . use core::error::Error; @@ -24,7 +24,7 @@ use core::fmt::{self, Display, Formatter}; /// An invalid UTF-8 sequence was encountered. #[derive(Debug)] -#[non_exhaustive] +#[must_use] pub struct Utf8Error { /// The invalid UTF-8 octet. pub value: u8, diff --git a/bzipper/src/i_stream/mod.rs b/librum/src/i_stream/mod.rs similarity index 86% rename from bzipper/src/i_stream/mod.rs rename to librum/src/i_stream/mod.rs index ced337d..b1d6586 100644 --- a/bzipper/src/i_stream/mod.rs +++ b/librum/src/i_stream/mod.rs @@ -1,22 +1,22 @@ // Copyright 2024 Gabriel Bjørnager Jensen. // -// This file is part of bZipper. +// This file is part of Librum. // -// bZipper is free software: you can redistribute -// it and/or modify it under the terms of the GNU +// Librum 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 +// Librum 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 +// er General Public License along with Librum. If // not, see . use core::slice; diff --git a/bzipper/src/lib.rs b/librum/src/lib.rs similarity index 65% rename from bzipper/src/lib.rs rename to librum/src/lib.rs index 237770f..cade1e3 100644 --- a/bzipper/src/lib.rs +++ b/librum/src/lib.rs @@ -1,27 +1,27 @@ // Copyright 2024 Gabriel Bjørnager Jensen. // -// This file is part of bZipper. +// This file is part of Librum. // -// bZipper is free software: you can redistribute -// it and/or modify it under the terms of the GNU +// Librum 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 +// Librum 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 +// er General Public License along with Librum. If // not, see . -#![doc(html_logo_url = "https://gitlab.com/bjoernager/bzipper/-/raw/master/doc-icon.svg")] +#![doc(html_logo_url = "https://gitlab.com/bjoernager/librum/-/raw/master/doc-icon.svg")] -//! bZipper is a Rust crate for cheaply serialising (encoding) and deserialising (decoding) data structures into binary streams +//! Librum is a Rust crate for cheaply serialising (encoding) and deserialising (decoding) data structures into binary streams //! //! What separates this crate from others such as [Bincode](https://crates.io/crates/bincode/) or [Postcard](https://crates.io/crates/postcard/) is that this crate is extensively optimised for *just* binary encodings (whilst the mentioned crates specifically use Serde and build on a more abstract data model). //! The original goal of this project was specifically to guarantee size constraints for encodings on a per-type basis at compile-time. @@ -34,28 +34,29 @@ //! //! # Performance //! -//! As bZipper is optimised exclusively for a single, binary format, it may outperform other libraries that are more generic in nature. +//! As Librum is optimised exclusively for a single, binary format, it may outperform other libraries that are more generic in nature. //! -//! The `bzipper_benchmarks` binary compares multiple scenarios using bZipper and other, similar crates. -//! According to my runs on an AMD Ryzen 7 3700X, these benchmarks indicate that bZipper outperform all of the tested crates -- as demonstrated in the following table: +//! The `librum-benchmarks` binary compares multiple scenarios using Librum and other, similar crates. +//! According to my runs on an AMD Ryzen 7 3700X, these benchmarks indicate that Librum outperform all of the tested crates -- as demonstrated in the following table: //! -//! | Benchmark | [Bincode] | [Borsh] | bZipper | [Ciborium] | [Postcard] | -//! | :--------------------------------- | --------: | ------: | ------: | ---------: | ---------: | -//! | `encode_u8` | 1.234 | 1.096 | 0.881 | 3.076 | 1.223 | -//! | `encode_struct_unit` | 0.000 | 0.000 | 0.000 | 0.516 | 0.000 | -//! | `encode_struct_unnamed` | 1.367 | 1.154 | 1.009 | 2.051 | 1.191 | -//! | `encode_struct_named` | 4.101 | 1.271 | 1.181 | 9.342 | 1.182 | -//! | `encode_enum_unit` | 0.306 | 0.008 | 0.000 | 2.304 | 0.004 | -//! | **Total time** → | 7.009 | 3.528 | 3.071 | 17.289 | 3.599 | -//! | **Total deviation (p.c.)** → | +128 | +15 | ±0 | +463 | +17 | +//! | Benchmark | [Bincode] | [Borsh] | Librum | [Postcard] | +//! | :--------------------------------- | --------: | ------: | ------: | ---------: | +//! | `encode_u8` | 1.306 | 1.315 | 1.150 | 1.304 | +//! | `encode_u32` | 1.321 | 1.317 | 1.146 | 3.016 | +//! | `encode_u128` | 2.198 | 2.103 | 1.509 | 6.376 | +//! | `encode_struct_unit` | 0.000 | 0.000 | 0.000 | 0.000 | +//! | `encode_struct_unnamed` | 1.362 | 1.448 | 1.227 | 2.659 | +//! | `encode_struct_named` | 3.114 | 1.530 | 0.969 | 3.036 | +//! | `encode_enum_unit` | 0.252 | 0.297 | 0.000 | 0.299 | +//! | **Total time** → | 9.553 | 8.010 | 6.001 | 16.691 | +//! | **Total deviation (p.c.)** → | +59 | +33 | ±0 | +178 | //! //! [Bincode]: https://crates.io/crates/bincode/ //! [Borsh]: https://crates.io/crates/borsh/ -//! [Ciborium]: https://crates.io/crates/ciborium/ //! [Postcard]: https://crates.io/crates/postcard/ //! //! All quantities are measured in seconds unless otherwise noted. -//! Please feel free to conduct your own tests of bZipper. +//! Please feel free to conduct your own tests of Librum. //! //! # Data model //! @@ -64,51 +65,71 @@ //! //! See specific types' implementations for notes on their data models. //! -//! **Note that the data model is currently not stabilised,** and may not necessarily be in the near future (before [specialisation](https://github.com/rust-lang/rust/issues/31844/)). +//! **Note that the data model is currently not stabilised,** and may not necessarily be in the near future (at least before [specialisation](https://github.com/rust-lang/rust/issues/31844/)). //! It may therefore be undesired to store encodings long-term. //! //! # Usage //! //! This crate revolves around the [`Encode`] and [`Decode`] traits which both handle conversions to and from byte streams. //! -//! Many standard types come implemented with bZipper, including most primitives as well as some standard library types such as [`Option`] and [`Result`]. +//! Many standard types come implemented with Librum, including most primitives as well as some standard library types such as [`Option`] and [`Result`]. //! Some [features](#feature-flags) enable an extended set of implementations. //! //! It is recommended in most cases to simply derive these two traits for custom types (although this is only supported with enumerations and structures -- not untagged unions). //! Here, each field is *chained* according to declaration order: //! //! ``` -//! use bzipper::{Buf, Decode, Encode, SizedEncode}; +//! use librum::{Buf, Decode, Encode}; //! -//! #[derive(Debug, Decode, PartialEq, SizedEncode)] -//! struct IoRegister { -//! addr: u32, -//! value: u16, +//! #[derive(Debug, Decode, Encode, PartialEq)] +//! struct Ints { +//! value0: u8, +//! value1: u16, +//! value2: u32, +//! value3: u64, +//! value4: u128, //! } //! -//! let mut buf = Buf::new(); +//! const VALUE: Ints = Ints { +//! value0: 0x00, +//! value1: 0x02_01, +//! value2: 0x06_05_04_03, +//! value3: 0x0E_0D_0C_0B_0A_09_08_07, +//! value4: 0x1E_1D_1C_1B_1A_19_18_17_16_15_14_13_12_11_10_0F, +//! }; //! -//! buf.write(IoRegister { addr: 0x04000000, value: 0x0402 }).unwrap(); +//! let mut buf = Buf::with_capacity(0x100); //! -//! assert_eq!(buf.len(), 0x6); -//! assert_eq!(buf, [0x04, 0x00, 0x00, 0x00, 0x04, 0x02].as_slice()); +//! buf.write(VALUE).unwrap(); //! -//! assert_eq!(buf.read().unwrap(), IoRegister { addr: 0x04000000, value: 0x0402 }); +//! assert_eq!(buf.len(), 0x1F); +//! +//! assert_eq!( +//! buf, +//! [ +//! 0x00, 0x02, 0x01, 0x06, 0x05, 0x04, 0x03, 0x0E, +//! 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x1E, +//! 0x1D, 0x1C, 0x1B, 0x1A, 0x19, 0x18, 0x17, 0x16, +//! 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x0F, +//! ].as_slice(), +//! ); +//! +//! assert_eq!(buf.read().unwrap(), VALUE); //! ``` //! //! ## Buffer types //! -//! The [`Encode`] and [`Decode`] traits both rely on streams for carrying the manipulated byte streams. +//! The [`Encode`] and [`Decode`] traits both rely on streams for carrying the manipulated bytes. //! //! These streams are separated into two type: [*O-streams*](OStream) (output streams) and [*i-streams*](IStream) (input streams). -//! Often, but not always, the [`Buf`] type is preferred over directly calling the [`encode`](Encode::encode) and [`decode`](Decode::decode) methods. +//! The [`Buf`] type can be used to handle these streams. //! //! ## Encoding //! -//! To encode an object directly using the [`Encode`] trait, simply allocate a buffer for the encoding and wrap it in an [`OStream`] object: +//! To encode an object directly using the `Encode` trait, simply allocate a buffer for the encoding and wrap it in an [`OStream`] object: //! //! ``` -//! use bzipper::{Encode, OStream, SizedEncode}; +//! use librum::{Encode, OStream, SizedEncode}; //! //! let mut buf = [0x00; char::MAX_ENCODED_SIZE]; //! let mut stream = OStream::new(&mut buf); @@ -121,7 +142,7 @@ //! Streams can also be used to chain multiple objects together: //! //! ``` -//! use bzipper::{Encode, OStream, SizedEncode}; +//! use librum::{Encode, OStream, SizedEncode}; //! //! let mut buf = [0x0; char::MAX_ENCODED_SIZE * 0x5]; //! let mut stream = OStream::new(&mut buf); @@ -154,7 +175,7 @@ //! To decode a byte array, simply call the [`decode`](Decode::decode) method with an [`IStream`] object: //! //! ``` -//! use bzipper::{Decode, IStream}; +//! use librum::{Decode, IStream}; //! //! let data = [0x45, 0x54]; //! let mut stream = IStream::new(&data); @@ -180,13 +201,13 @@ //! A UDP server/client for geographic data: //! //! ``` -//! use bzipper::{Buf, Decode, SizedEncode}; +//! use librum::{Buf, Encode, Decode, SizedEncode}; //! use std::io; //! use std::net::{SocketAddr, ToSocketAddrs, UdpSocket}; //! use std::thread::spawn; //! //! // City, region, etc.: -//! #[derive(Clone, Copy, Debug, Decode, Eq, PartialEq, SizedEncode)] +//! #[derive(Clone, Copy, Debug, Decode, Encode, Eq, PartialEq, SizedEncode)] //! enum Area { //! AlQuds, //! Byzantion, @@ -196,7 +217,7 @@ //! } //! //! // Client-to-server message: -//! #[derive(Debug, Decode, PartialEq, SizedEncode)] +//! #[derive(Debug, Decode, Encode, PartialEq, SizedEncode)] //! enum Request { //! AtmosphericHumidity { area: Area }, //! AtmosphericPressure { area: Area }, @@ -205,7 +226,7 @@ //! } //! //! // Server-to-client message: -//! #[derive(Debug, Decode, PartialEq, SizedEncode)] +//! #[derive(Debug, Decode, Encode, PartialEq, SizedEncode)] //! enum Response { //! AtmosphericHumidity(f64), //! AtmosphericPressure(f64), // Pascal @@ -281,25 +302,28 @@ //! //! # Feature flags //! -//! bZipper defines the following features: +//! Librum defines the following features: //! -//! * `alloc` (default): Enables the [`Buf`] type and implementations for e.g. [`Box`](alloc::boxed::Box) and [`Arc`](alloc::sync::Arc) -//! * `std` (default): Enables implementations for types such as [`Mutex`](std::sync::Mutex) and [`RwLock`](std::sync::RwLock) +//! * *`alloc`: Enables the [`Buf`] type and implementations for e.g. [`Box`](alloc::boxed::Box) and [`Arc`](alloc::sync::Arc) +//! * *`proc-macro`: Pulls the procedural macros from the [`librum_macros`](https://crates.io/crates/librum_macros/) crate +//! * *`std`: Enables implementations for types such as [`Mutex`](std::sync::Mutex) and [`RwLock`](std::sync::RwLock) +//! +//! Features marked with * are enabled by default. //! //! # Documentation //! -//! bZipper has its documentation written in-source for use by `rustdoc`. -//! See [Docs.rs](https://docs.rs/bzipper/latest/bzipper/) for an on-line, rendered instance. +//! Librum has its documentation written in-source for use by `rustdoc`. +//! See [Docs.rs](https://docs.rs/librum/latest/librum/) for an on-line, rendered instance. //! //! Currently, these docs make use of some unstable features for the sake of readability. //! The nightly toolchain is therefore required when rendering them. //! //! # Contribution //! -//! bZipper does not accept source code contributions at the moment. +//! Librum does not accept source code contributions at the moment. //! This is a personal choice by the maintainer and may be undone in the future. //! -//! Do however feel free to open up an issue on [`GitLab`](https://gitlab.com/bjoernager/bzipper/issues/) or (preferably) [`GitHub`](https://github.com/bjoernager/bzipper/issues/) if you feel the need to express any concerns over the project. +//! Do however feel free to open up an issue on [`GitLab`](https://gitlab.com/bjoernager/librum/issues/) or (preferably) [`GitHub`](https://github.com/bjoernager/librum/issues/) if you feel the need to express any concerns over the project. //! //! # Copyright & Licence //! @@ -319,7 +343,7 @@ #![cfg_attr(doc, feature(doc_cfg, rustdoc_internals))] // For use in macros: -extern crate self as bzipper; +extern crate self as librum; #[cfg(feature = "alloc")] extern crate alloc; @@ -330,20 +354,30 @@ extern crate std; /// Implements [`Decode`] for the provided type. /// /// This macro assumes the same format used by the equivalent [`Encode`](derive@Encode) macro. +#[cfg(feature = "proc-macro")] +#[cfg_attr(doc, doc(cfg(feature = "proc-macro")))] #[doc(inline)] -pub use bzipper_macros::Decode; +pub use librum_macros::Decode; /// Implements [`Encode`] for the provided type. /// -/// Note that if all fields additionally implement [`SizedEncode`](trait@SizedEncode), then the [`SizedEncode`](derive@SizedEncode) derive macro is usually prefered instead. +/// This derive macro assumes that all fields implement Encode<[Error]: [Into]<[GenericEncodeError]>>. +/// If this is **not** the case, then the trait should be implemented manually instead. +/// +/// [Error]: Encode::Error +/// [GenericEncodeError]: crate::error::GenericEncodeError +/// +/// Do also consider deriving [`SizedEncode`](derive@SizedEncode) -- if possible. /// /// # Structs /// /// For structures, each element is chained in **order of declaration.** +/// If the structure is a unit structure (i.e. it has *no* fields) then it is encoded equivalently to the [unit] type. +/// /// For example, the following struct will encode its field `foo` followed by `bar`: /// /// ``` -/// use bzipper::Encode; +/// use librum::Encode; /// /// #[derive(Encode)] /// struct FooBar { @@ -352,9 +386,9 @@ pub use bzipper_macros::Decode; /// } /// ``` /// -/// This should be kept in mind when changing the structure's declaration as doing so may invalidate previous encodings. +/// This should be kept in mind when changing the structure's declarationm as doing so may invalidate previous encodings. /// -/// If the structure is a unit structure (i.e. it has *no* fields) then it is encoded equivalently to the [unit] type. +/// The [`Error`](Encode::Error) type will in all cases just be `GenericEncodeError`. /// /// # Enums /// @@ -365,9 +399,9 @@ pub use bzipper_macros::Decode; /// Unspecified discriminants then increment the previous variant's discriminant: /// /// ``` -/// use bzipper::{Buf, SizedEncode}; +/// use librum::{Buf, Encode}; /// -/// #[derive(SizedEncode)] +/// #[derive(Encode)] /// enum Num { /// Two = 0x2, /// @@ -378,7 +412,7 @@ pub use bzipper_macros::Decode; /// One, /// } /// -/// let mut buf = Buf::new(); +/// let mut buf = Buf::with_capacity(size_of::()); /// /// buf.write(Num::Zero).unwrap(); /// assert_eq!(buf, [0x00, 0x00].as_slice()); @@ -396,25 +430,36 @@ pub use bzipper_macros::Decode; /// Variants with fields are encoded exactly like structures. /// That is, each field is chained in order of declaration. /// +/// For error handling, the `Error` type is defined as: +/// +/// [EnumEncodeError]<<Repr as Encode>::Error, GenericEncodeError>, +/// +/// [EnumEncodeError]: crate::error::GenericEncodeError +/// +/// wherein `Repr` is the enumeration's representation. +/// /// # Unions /// /// Unions cannot derive `Encode` due to the uncertainty of their contents. /// The trait should therefore be implemented manually for such types. +#[cfg(feature = "proc-macro")] +#[cfg_attr(doc, doc(cfg(feature = "proc-macro")))] #[doc(inline)] -pub use bzipper_macros::Encode; +pub use librum_macros::Encode; -/// Implements [`Encode`](trait@Encode) and [`SizedEncode`] for the given type. +/// Implements [`Encode`](trait@Encode) using the default implementation. /// -/// See also the [`Encode`](derive@Encode) derive macro for how the resulting encoder is implemented. +/// For simple structures, the value of [`MAX_ENCODED_SIZE`](SizedEncode::MAX_ENCODED_SIZE) is set as the combined value of T*n*::MAX_ENCODED_SIZE wherein T*n* is the type of each field. /// -/// For simple structures, the value of [`MAX_ENCODED_SIZE`](SizedEncode::MAX_ENCODED_SIZE) is set to the combined value of all fields' own definition. -/// -/// For enumerations, each variant has its own `MAX_ENCODED_SIZE` value calculated as if it was an equivalent structure (additionally containing the discriminant). -/// The largest of these values is then chosen as the enumeration type's actual `MAX_ENCODED_SIZE` value. +/// For enumerations, the value is set such that each variant is treated like a structure (with the discriminant as an extra field) and where the variant that produces the largest `MAX_ENCODED_SIZE` is chosen. /// /// As untagged unions cannot derive `Encode`, `SizedEncode` also cannot be derived for them. +/// +/// Do remember that deriving this trait is only recommended +#[cfg(feature = "proc-macro")] +#[cfg_attr(doc, doc(cfg(feature = "proc-macro")))] #[doc(inline)] -pub use bzipper_macros::SizedEncode; +pub use librum_macros::SizedEncode; macro_rules! use_mod { ($vis:vis $name:ident$(,)?) => { @@ -425,9 +470,11 @@ macro_rules! use_mod { pub(crate) use use_mod; use_mod!(pub decode); +use_mod!(pub decode_borrowed); use_mod!(pub encode); use_mod!(pub i_stream); use_mod!(pub o_stream); +use_mod!(pub primitive_discriminant); use_mod!(pub sized_encode); use_mod!(pub sized_iter); use_mod!(pub sized_slice); diff --git a/bzipper/src/o_stream/mod.rs b/librum/src/o_stream/mod.rs similarity index 85% rename from bzipper/src/o_stream/mod.rs rename to librum/src/o_stream/mod.rs index cf63bcf..d105f25 100644 --- a/bzipper/src/o_stream/mod.rs +++ b/librum/src/o_stream/mod.rs @@ -1,22 +1,22 @@ // Copyright 2024 Gabriel Bjørnager Jensen. // -// This file is part of bZipper. +// This file is part of Librum. // -// bZipper is free software: you can redistribute -// it and/or modify it under the terms of the GNU +// Librum 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 +// Librum 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 +// er General Public License along with Librum. If // not, see . use core::ptr::copy_nonoverlapping; @@ -65,7 +65,6 @@ impl<'a> OStream<'a> { /// Closes the stream. /// /// The total ammount of bytes written is returned. - #[expect(clippy::must_use_candidate)] #[inline(always)] pub const fn close(self) -> usize { let Self { pos, .. } = self; diff --git a/librum/src/primitive_discriminant/mod.rs b/librum/src/primitive_discriminant/mod.rs new file mode 100644 index 0000000..cb8b782 --- /dev/null +++ b/librum/src/primitive_discriminant/mod.rs @@ -0,0 +1,73 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Librum. +// +// Librum 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. +// +// Librum 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 Librum. If +// not, see . + +mod sealed { + /// Denotes a primitive, integral discriminant type. + /// + /// See the public [`PrimitiveDiscriminant`](crate::PrimitiveDiscriminant) trait for more information. + pub trait PrimitiveDiscriminant { + /// Interprets the discriminant value as `u128`. + /// + /// The returned value has exactly the same representation as the original value except that it is zero-extended to fit. + #[must_use] + fn to_u128(self) -> u128; + } +} + +pub(crate) use sealed::PrimitiveDiscriminant as SealedPrimitiveDiscriminant; + +/// Denotes a primitive, integral discriminant type. +/// +/// This type is specifically defined as a type which may be used as a representation in the `repr` attribute, i.e. [`u8`], [`i8`], [`u16`], [`i16`], [`u32`], [`i32`], [`u64`], [`i64`], [`usize`], and [`isize`]. +/// +/// On nightly, this additionally includes [`u128`] and [`i128`] (see [`repr128`](https://github.com/rust-lang/rust/issues/56071/)). +/// Note that this trait is implemented for these two types regardless. +/// +/// Internally -- specifically in the [`GenericDecodeError`](crate::error::GenericDecodeError) enumeration -- this trait guarantees representability in the `u128` type. +pub trait PrimitiveDiscriminant: SealedPrimitiveDiscriminant + Sized { } + +macro_rules! impl_primitive_discriminant { + ($ty:ty) => { + impl ::librum::SealedPrimitiveDiscriminant for $ty { + #[allow(clippy::cast_lossless)] + #[inline(always)] + #[must_use] + fn to_u128(self) -> u128 { + self as u128 + } + } + + impl ::librum::PrimitiveDiscriminant for $ty { } + }; +} + +impl_primitive_discriminant!(u8); +impl_primitive_discriminant!(i8); +impl_primitive_discriminant!(u16); +impl_primitive_discriminant!(i16); +impl_primitive_discriminant!(u32); +impl_primitive_discriminant!(i32); +impl_primitive_discriminant!(u64); +impl_primitive_discriminant!(i64); +impl_primitive_discriminant!(u128); +impl_primitive_discriminant!(i128); +impl_primitive_discriminant!(usize); +impl_primitive_discriminant!(isize); diff --git a/bzipper/src/sized_encode/mod.rs b/librum/src/sized_encode/mod.rs similarity index 68% rename from bzipper/src/sized_encode/mod.rs rename to librum/src/sized_encode/mod.rs index ae5d27a..7efaa9e 100644 --- a/bzipper/src/sized_encode/mod.rs +++ b/librum/src/sized_encode/mod.rs @@ -1,26 +1,26 @@ // Copyright 2024 Gabriel Bjørnager Jensen. // -// This file is part of bZipper. +// This file is part of Librum. // -// bZipper is free software: you can redistribute -// it and/or modify it under the terms of the GNU +// Librum 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 +// Librum 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 +// er General Public License along with Librum. If // not, see . #[cfg(test)] -mod test; +mod tests; use crate::Encode; @@ -56,7 +56,7 @@ use alloc::boxed::Box; #[cfg(feature = "alloc")] use alloc::rc::Rc; -#[cfg(feature = "alloc")] +#[cfg(all(feature = "alloc", target_has_atomic = "ptr"))] use alloc::sync::Arc; #[cfg(feature = "std")] @@ -70,157 +70,159 @@ use std::time::SystemTime; /// When using [`Encode`], the size of the resulting encoding cannot always be known beforehand. /// This trait defines an upper bound for these sizes. /// -/// # Safety -/// -/// Users of the `Encode` and [`Decode`](crate::Decode) traits may assume that the [`MAX_ENCODED_SIZE`](Self::MAX_ENCODED_SIZE) constant is properly defined and that no encoding will be larger than this value. -/// Implementors must therefore guarantee that **no** call to [`encode`](Encode::encode) or [`decode`](bzipper::Decode::decode) consumes more bytes than specified by this constant. -pub unsafe trait SizedEncode: Encode + Sized { +/// Note that -- in practice -- this trait is **not** strictly enforceable. +/// Users of the `Encode` and [`Decode`](crate::Decode) traits may assume that this trait is properly defined, but should still leave room for the possibility that it isn't. +pub trait SizedEncode: Encode + Sized { /// The maximum guaranteed amount of bytes that can result from an encoding. /// - /// Implementors of this trait should make sure that no encoding (or decoding) uses more than the amount specified by this constant. + /// Implementors of this trait should make sure that no encoding (or decoding) consumes more than the amount specified by this constant. const MAX_ENCODED_SIZE: usize; } -unsafe impl SizedEncode for &T { +impl SizedEncode for &T { const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE; } -unsafe impl SizedEncode for &mut T { +impl SizedEncode for &mut T { const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE; } /// Implemented for tuples with up to twelve members. #[cfg_attr(doc, doc(fake_variadic))] -unsafe impl SizedEncode for (T, ) { +impl SizedEncode for (T, ) { #[doc(hidden)] const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE; } -unsafe impl SizedEncode for [T; N] { +impl SizedEncode for [T; N] { const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE * N; } -#[cfg(feature = "alloc")] -#[cfg_attr(doc, doc(cfg(feature = "alloc")))] -unsafe impl SizedEncode for Arc { +#[cfg(all(feature = "alloc", target_has_atomic = "ptr"))] +#[cfg_attr(doc, doc(cfg(all(feature = "alloc", target_has_atomic = "ptr"))))] +impl SizedEncode for Arc { const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE; } -unsafe impl SizedEncode for bool { +impl SizedEncode for bool { const MAX_ENCODED_SIZE: usize = u8::MAX_ENCODED_SIZE; } -unsafe impl SizedEncode for Bound { +impl SizedEncode for Bound { const MAX_ENCODED_SIZE: usize = 0x0; } #[cfg(feature = "alloc")] #[cfg_attr(doc, doc(cfg(feature = "alloc")))] -unsafe impl SizedEncode for Box { +impl SizedEncode for Box { const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE; } -unsafe impl SizedEncode for Cell { +impl SizedEncode for Cell { const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE; } -unsafe impl SizedEncode for char { +impl SizedEncode for char { const MAX_ENCODED_SIZE: usize = u32::MAX_ENCODED_SIZE; } #[cfg(feature = "alloc")] #[cfg_attr(doc, doc(cfg(feature = "alloc")))] -unsafe impl SizedEncode for Cow<'_, T> { +impl SizedEncode for Cow<'_, T> { const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE; } -unsafe impl SizedEncode for Duration { +impl SizedEncode for Duration { const MAX_ENCODED_SIZE: usize = u64::MAX_ENCODED_SIZE + u32::MAX_ENCODED_SIZE; } -unsafe impl SizedEncode for Infallible { +impl SizedEncode for Infallible { const MAX_ENCODED_SIZE: usize = 0x0; } -unsafe impl SizedEncode for IpAddr { +impl SizedEncode for IpAddr { const MAX_ENCODED_SIZE: usize = u8::MAX_ENCODED_SIZE + Ipv6Addr::MAX_ENCODED_SIZE; } -unsafe impl SizedEncode for Ipv4Addr { +impl SizedEncode for Ipv4Addr { const MAX_ENCODED_SIZE: usize = u32::MAX_ENCODED_SIZE; } -unsafe impl SizedEncode for Ipv6Addr { +impl SizedEncode for Ipv6Addr { const MAX_ENCODED_SIZE: usize = u128::MAX_ENCODED_SIZE; } -unsafe impl SizedEncode for isize { +impl SizedEncode for isize { const MAX_ENCODED_SIZE: usize = i16::MAX_ENCODED_SIZE; } -unsafe impl SizedEncode for LazyCell { +impl SizedEncode for LazyCell { const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE; } #[cfg(feature = "std")] #[cfg_attr(doc, doc(cfg(feature = "std")))] -unsafe impl SizedEncode for LazyLock { +impl SizedEncode for LazyLock { const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE; } #[cfg(feature = "std")] #[cfg_attr(doc, doc(cfg(feature = "std")))] -unsafe impl SizedEncode for Mutex { +impl SizedEncode for Mutex { const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE; } -unsafe impl SizedEncode for Option { +impl SizedEncode for Option { const MAX_ENCODED_SIZE: usize = bool::MAX_ENCODED_SIZE + T::MAX_ENCODED_SIZE; } -unsafe impl SizedEncode for PhantomData { +impl SizedEncode for PhantomData { const MAX_ENCODED_SIZE: usize = 0x0; } -unsafe impl SizedEncode for Range { +impl SizedEncode for Range { const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE * 0x2; } -unsafe impl SizedEncode for RangeFrom { +impl SizedEncode for RangeFrom { const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE; } -unsafe impl SizedEncode for RangeFull { +impl SizedEncode for RangeFull { const MAX_ENCODED_SIZE: usize = 0x0; } -unsafe impl SizedEncode for RangeInclusive { +impl SizedEncode for RangeInclusive { const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE * 0x2; } -unsafe impl SizedEncode for RangeTo { +impl SizedEncode for RangeTo { const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE; } -unsafe impl SizedEncode for RangeToInclusive { +impl SizedEncode for RangeToInclusive { const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE; } #[cfg(feature = "alloc")] #[cfg_attr(doc, doc(cfg(feature = "alloc")))] -unsafe impl SizedEncode for Rc { +impl SizedEncode for Rc { const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE; } -unsafe impl SizedEncode for RefCell { +impl SizedEncode for RefCell { const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE; } -unsafe impl SizedEncode for core::result::Result { +impl SizedEncode for core::result::Result +where + T: SizedEncode + Encode, + E: SizedEncode + Encode, +{ const MAX_ENCODED_SIZE: usize = bool::MAX_ENCODED_SIZE + if size_of::() > size_of::() { size_of::() } else { size_of::() }; @@ -228,24 +230,24 @@ unsafe impl SizedEncode for core::result::Result #[cfg(feature = "std")] #[cfg_attr(doc, doc(cfg(feature = "std")))] -unsafe impl SizedEncode for RwLock { +impl SizedEncode for RwLock { const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE; } -unsafe impl SizedEncode for Saturating { +impl SizedEncode for Saturating { const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE; } -unsafe impl SizedEncode for SocketAddr { +impl SizedEncode for SocketAddr { const MAX_ENCODED_SIZE: usize = u8::MAX_ENCODED_SIZE + SocketAddrV6::MAX_ENCODED_SIZE; } -unsafe impl SizedEncode for SocketAddrV4 { +impl SizedEncode for SocketAddrV4 { const MAX_ENCODED_SIZE: usize = Ipv4Addr::MAX_ENCODED_SIZE + u16::MAX_ENCODED_SIZE; } /// This implementation encodes the address's bits followed by the port number, all of which in big-endian. -unsafe impl SizedEncode for SocketAddrV6 { +impl SizedEncode for SocketAddrV6 { const MAX_ENCODED_SIZE: usize = Ipv6Addr::MAX_ENCODED_SIZE + u16::MAX_ENCODED_SIZE @@ -255,25 +257,25 @@ unsafe impl SizedEncode for SocketAddrV6 { #[cfg(feature = "std")] #[cfg_attr(doc, doc(cfg(feature = "std")))] -unsafe impl SizedEncode for SystemTime { +impl SizedEncode for SystemTime { const MAX_ENCODED_SIZE: usize = i64::MAX_ENCODED_SIZE; } -unsafe impl SizedEncode for () { +impl SizedEncode for () { const MAX_ENCODED_SIZE: usize = 0x0; } -unsafe impl SizedEncode for usize { +impl SizedEncode for usize { const MAX_ENCODED_SIZE: Self = u16::MAX_ENCODED_SIZE; } -unsafe impl SizedEncode for Wrapping { +impl SizedEncode for Wrapping { const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE; } macro_rules! impl_numeric { ($ty:ty$(,)?) => { - unsafe impl ::bzipper::SizedEncode for $ty { + impl ::librum::SizedEncode for $ty { const MAX_ENCODED_SIZE: usize = size_of::<$ty>(); } }; @@ -284,16 +286,18 @@ macro_rules! impl_tuple { $($tys:ident),+$(,)? } => { #[doc(hidden)] - unsafe impl<$($tys: ::bzipper::SizedEncode, )*> ::bzipper::SizedEncode for ($($tys, )*) { - const MAX_ENCODED_SIZE: usize = 0x0 $(+ <$tys as ::bzipper::SizedEncode>::MAX_ENCODED_SIZE)*; + impl<$($tys, )* E> ::librum::SizedEncode for ($($tys, )*) + where + $($tys: SizedEncode + Encode, )* { + const MAX_ENCODED_SIZE: usize = 0x0 $(+ <$tys as ::librum::SizedEncode>::MAX_ENCODED_SIZE)*; } }; } macro_rules! impl_non_zero { ($ty:ty$(,)?) => { - unsafe impl ::bzipper::SizedEncode for ::core::num::NonZero<$ty> { - const MAX_ENCODED_SIZE: usize = <$ty as ::bzipper::SizedEncode>::MAX_ENCODED_SIZE; + impl ::librum::SizedEncode for ::core::num::NonZero<$ty> { + const MAX_ENCODED_SIZE: usize = <$ty as ::librum::SizedEncode>::MAX_ENCODED_SIZE; } }; } @@ -306,8 +310,8 @@ macro_rules! impl_atomic { } => { #[cfg(target_has_atomic = $width)] #[cfg_attr(doc, doc(cfg(target_has_atomic = $width)))] - unsafe impl ::bzipper::SizedEncode for $atomic_ty { - const MAX_ENCODED_SIZE: usize = <$ty as ::bzipper::SizedEncode>::MAX_ENCODED_SIZE; + impl ::librum::SizedEncode for $atomic_ty { + const MAX_ENCODED_SIZE: usize = <$ty as ::librum::SizedEncode>::MAX_ENCODED_SIZE; } }; } diff --git a/bzipper/src/sized_encode/test.rs b/librum/src/sized_encode/tests.rs similarity index 78% rename from bzipper/src/sized_encode/test.rs rename to librum/src/sized_encode/tests.rs index 3e88812..f1526dc 100644 --- a/bzipper/src/sized_encode/test.rs +++ b/librum/src/sized_encode/tests.rs @@ -1,24 +1,25 @@ // Copyright 2024 Gabriel Bjørnager Jensen. // -// This file is part of bzipper. -//test!(you can redistribut => []); -// it and/or modify it under the terms of the GNU +// This file is part of Librum. +// +// Librum 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 +// Librum 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 +// er General Public License along with Librum. If // not, see . -use bzipper::{SizedStr, SizedEncode}; +use librum::{Encode, SizedStr, SizedEncode}; use std::convert::Infallible; use std::marker::PhantomData; use std::net::{ @@ -31,28 +32,14 @@ use std::net::{ }; use std::num::NonZero; -#[derive(SizedEncode)] -struct Foo(char); - -#[derive(SizedEncode)] -#[expect(dead_code)] -#[repr(u8)] // Not honoured. -enum Bar { - Unit = 0x45, - - Pretty(bool) = 127, - - Teacher { initials: [char; 0x3] }, +macro_rules! assert_encoded_size { + ($ty:ty, $value:expr$(,)?) => {{ + assert_eq!(<$ty as ::librum::SizedEncode>::MAX_ENCODED_SIZE, $value); + }}; } #[test] fn test_sized_encode() { - macro_rules! assert_encoded_size { - ($ty:ty, $value:expr$(,)?) => {{ - assert_eq!(<$ty as ::bzipper::SizedEncode>::MAX_ENCODED_SIZE, $value); - }}; - } - assert_encoded_size!(bool, 0x1); assert_encoded_size!(char, 0x4); assert_encoded_size!(f32, 0x4); @@ -63,7 +50,7 @@ fn test_sized_encode() { assert_encoded_size!(i64, 0x8); assert_encoded_size!(i8, 0x1); assert_encoded_size!(isize, 0x2); - assert_encoded_size!(SizedStr::<0x45>, 0x47); + assert_encoded_size!(SizedStr::<0x45>, 0x47); assert_encoded_size!(Infallible, 0x0); assert_encoded_size!(IpAddr, 0x11); assert_encoded_size!(Ipv4Addr, 0x4); @@ -91,7 +78,24 @@ fn test_sized_encode() { assert_encoded_size!(u8, 0x1); assert_encoded_size!(usize, 0x2); assert_encoded_size!((), 0x0); +} + +#[test] +fn test_sized_encode_derive() { + #[derive(Encode, SizedEncode)] + struct Foo(char); + + #[derive(Encode, SizedEncode)] + #[expect(dead_code)] + #[repr(i64)] + enum Bar { + Unit = 0x45, + + Pretty(bool) = 127, + + Teacher { initials: [char; 0x3] }, + } assert_encoded_size!(Foo, 0x4); - assert_encoded_size!(Bar, 0xE); + assert_encoded_size!(Bar, 0x14); } diff --git a/bzipper/src/sized_iter/mod.rs b/librum/src/sized_iter/mod.rs similarity index 91% rename from bzipper/src/sized_iter/mod.rs rename to librum/src/sized_iter/mod.rs index 48fc15d..8c38601 100644 --- a/bzipper/src/sized_iter/mod.rs +++ b/librum/src/sized_iter/mod.rs @@ -1,26 +1,26 @@ // Copyright 2024 Gabriel Bjørnager Jensen. // -// This file is part of bZipper. +// This file is part of Librum. // -// bZipper is free software: you can redistribute -// it and/or modify it under the terms of the GNU +// Librum 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 +// Librum 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 +// er General Public License along with Librum. If // not, see . #[cfg(test)] -mod test; +mod tests; use core::iter::{DoubleEndedIterator, ExactSizeIterator, FusedIterator}; use core::mem::MaybeUninit; @@ -87,7 +87,6 @@ impl AsRef<[T]> for SizedIter { } impl Clone for SizedIter { - #[expect(clippy::borrow_deref_ref)] // Clippy is gaslighting me into believing pointers and references are identical??? #[inline] fn clone(&self) -> Self { let mut buf: [MaybeUninit; N] = unsafe { MaybeUninit::uninit().assume_init() }; diff --git a/bzipper/src/sized_iter/test.rs b/librum/src/sized_iter/tests.rs similarity index 86% rename from bzipper/src/sized_iter/test.rs rename to librum/src/sized_iter/tests.rs index cf3f028..3b8034f 100644 --- a/bzipper/src/sized_iter/test.rs +++ b/librum/src/sized_iter/tests.rs @@ -1,25 +1,25 @@ // Copyright 2024 Gabriel Bjørnager Jensen. // -// This file is part of bZipper. +// This file is part of Librum. // -// bZipper is free software: you can redistribute -// it and/or modify it under the terms of the GNU +// Librum 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 +// Librum 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 +// er General Public License along with Librum. If // not, see . -use bzipper::{SizedSlice, SizedStr}; +use librum::{SizedSlice, SizedStr}; #[test] fn test_sized_iter_clone() { diff --git a/librum/src/sized_slice/cmp.rs b/librum/src/sized_slice/cmp.rs new file mode 100644 index 0000000..b73c012 --- /dev/null +++ b/librum/src/sized_slice/cmp.rs @@ -0,0 +1,96 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Librum. +// +// Librum 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. +// +// Librum 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 Librum. If +// not, see . + +use crate::SizedSlice; + +use core::cmp::Ordering; + +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + +impl Eq for SizedSlice { } + +impl Ord for SizedSlice { + #[inline(always)] + fn cmp(&self, other: &Self) -> Ordering { + self.as_slice().cmp(other.as_slice()) + } +} + +impl, U: PartialEq, const N: usize, const M: usize> PartialEq> for SizedSlice { + #[inline(always)] + fn eq(&self, other: &SizedSlice) -> bool { + self.as_slice() == other.as_slice() + } +} + +impl, U: PartialEq, const N: usize, const M: usize> PartialEq<[U; M]> for SizedSlice { + #[inline(always)] + fn eq(&self, other: &[U; M]) -> bool { + self.as_slice() == other.as_slice() + } +} + +impl, U: PartialEq, const N: usize> PartialEq<&[U]> for SizedSlice { + #[inline(always)] + fn eq(&self, other: &&[U]) -> bool { + self.as_slice() == *other + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl, U: PartialEq, const N: usize> PartialEq> for SizedSlice { + #[inline(always)] + fn eq(&self, other: &Vec) -> bool { + self.as_slice() == other.as_slice() + } +} + +impl PartialOrd> for SizedSlice { + #[inline(always)] + fn partial_cmp(&self, other: &SizedSlice) -> Option { + self.as_slice().partial_cmp(other.as_slice()) + } +} + +impl PartialOrd<[T; M]> for SizedSlice { + #[inline(always)] + fn partial_cmp(&self, other: &[T; M]) -> Option { + self.as_slice().partial_cmp(other.as_slice()) + } +} + +impl PartialOrd<&[T]> for SizedSlice { + #[inline(always)] + fn partial_cmp(&self, other: &&[T]) -> Option { + self.as_slice().partial_cmp(*other) + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl PartialOrd> for SizedSlice { + #[inline(always)] + fn partial_cmp(&self, other: &Vec) -> Option { + self.as_slice().partial_cmp(other.as_slice()) + } +} \ No newline at end of file diff --git a/librum/src/sized_slice/conv.rs b/librum/src/sized_slice/conv.rs new file mode 100644 index 0000000..a3cd592 --- /dev/null +++ b/librum/src/sized_slice/conv.rs @@ -0,0 +1,230 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Librum. +// +// Librum 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. +// +// Librum 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 Librum. If +// not, see . + +use crate::SizedSlice; +use crate::error::SizeError; + +use core::borrow::{Borrow, BorrowMut}; +use core::mem::MaybeUninit; +use core::ops::{Deref, DerefMut}; +use core::ptr::copy_nonoverlapping; +use core::slice; + +#[cfg(feature = "alloc")] +use alloc::alloc::{alloc, Layout}; + +#[cfg(feature = "alloc")] +use alloc::boxed::Box; + +#[cfg(feature = "alloc")] +use alloc::vec::Vec; + +impl SizedSlice { + /// Constructs a fixed-size vector from raw parts. + /// + /// The provided parts are not tested in any way. + /// + /// # Safety + /// + /// The value of `len` may not exceed that of `N`. + /// Additionally, all elements of `buf` in the range specified by `len` must be initialised. + /// + /// If any of these requirements are violated, behaviour is undefined. + #[inline(always)] + #[must_use] + pub const unsafe fn from_raw_parts(buf: [MaybeUninit; N], len: usize) -> Self { + debug_assert!(len <= N, "cannot construct vector longer than its capacity"); + + Self { buf, len } + } + + /// Gets a pointer to the first element. + /// + /// The pointed-to element may not necessarily be initialised. + /// See [`len`](Self::len) for more information. + #[inline(always)] + #[must_use] + pub const fn as_ptr(&self) -> *const T { + self.buf.as_ptr().cast() + } + + /// Gets a mutable pointer to the first element. + /// + /// The pointed-to element may not necessarily be initialised. + /// See [`len`](Self::len) for more information. + #[inline(always)] + #[must_use] + pub const fn as_mut_ptr(&mut self) -> *mut T { + self.buf.as_mut_ptr().cast() + } + + /// Borrows the vector as a slice. + /// + /// The range of the returned slice only includes the elements specified by [`len`](Self::len). + #[inline(always)] + #[must_use] + pub const fn as_slice(&self) -> &[T] { + let ptr = self.as_ptr(); + let len = self.len(); + + unsafe { slice::from_raw_parts(ptr, len) } + } + + /// Borrows the vector as a mutable slice. + /// + /// The range of the returned slice only includes the elements specified by [`len`](Self::len). + #[inline(always)] + #[must_use] + pub const fn as_mut_slice(&mut self) -> &mut [T] { + let ptr = self.as_mut_ptr(); + let len = self.len(); + + unsafe { slice::from_raw_parts_mut(ptr, len) } + } + + /// Destructs the vector into its raw parts. + /// + /// The returned values are valid to pass on to [`from_raw_parts`](Self::from_raw_parts). + #[inline(always)] + #[must_use] + pub const fn into_raw_parts(self) -> ([MaybeUninit; N], usize) { + let Self { buf, len } = self; + (buf, len) + } + + /// Converts the vector into a boxed slice. + /// + /// The vector is reallocated using the global allocator. + #[cfg(feature = "alloc")] + #[cfg_attr(doc, doc(cfg(feature = "alloc")))] + #[must_use] + pub fn into_boxed_slice(self) -> Box<[T]> { + let (buf, len) = self.into_raw_parts(); + + unsafe { + let layout = Layout::array::(len).unwrap(); + let ptr = alloc(layout).cast::(); + + assert!(!ptr.is_null(), "allocation failed"); + + copy_nonoverlapping(buf.as_ptr().cast(), ptr, len); + + let slice = core::ptr::slice_from_raw_parts_mut(ptr, len); + Box::from_raw(slice) + + // `self.buf` is dropped without destructors being + // run. + } + } + + /// Converts the vector into a dynamic vector. + /// + /// The vector is reallocated using the global allocator. + #[cfg(feature = "alloc")] + #[cfg_attr(doc, doc(cfg(feature = "alloc")))] + #[inline(always)] + #[must_use] + pub fn into_vec(self) -> Vec { + self.into_boxed_slice().into_vec() + } +} + +impl AsMut<[T]> for SizedSlice { + #[inline(always)] + fn as_mut(&mut self) -> &mut [T] { + self.as_mut_slice() + } +} + +impl AsRef<[T]> for SizedSlice { + #[inline(always)] + fn as_ref(&self) -> &[T] { + self.as_slice() + } +} + +impl Borrow<[T]> for SizedSlice { + #[inline(always)] + fn borrow(&self) -> &[T] { + self.as_slice() + } +} + +impl BorrowMut<[T]> for SizedSlice { + #[inline(always)] + fn borrow_mut(&mut self) -> &mut [T] { + self.as_mut_slice() + } +} + +impl Deref for SizedSlice { + type Target = [T]; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + self.as_slice() + } +} + +impl DerefMut for SizedSlice { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + self.as_mut_slice() + } +} + +impl From<[T; N]> for SizedSlice { + #[inline(always)] + fn from(value: [T; N]) -> Self { + unsafe { + let buf = value.as_ptr().cast::<[MaybeUninit; N]>().read(); + + Self { buf, len: N } + } + } +} + +impl TryFrom<&[T]> for SizedSlice { + type Error = SizeError; + + #[inline(always)] + fn try_from(value: &[T]) -> Result { + Self::new(value) + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl From> for Box<[T]> { + #[inline(always)] + fn from(value: SizedSlice) -> Self { + value.into_boxed_slice() + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl From> for Vec { + #[inline(always)] + fn from(value: SizedSlice) -> Self { + value.into_vec() + } +} diff --git a/librum/src/sized_slice/iter.rs b/librum/src/sized_slice/iter.rs new file mode 100644 index 0000000..b3059a4 --- /dev/null +++ b/librum/src/sized_slice/iter.rs @@ -0,0 +1,79 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Librum. +// +// Librum 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. +// +// Librum 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 Librum. If +// not, see . + +use crate::{SizedIter, SizedSlice}; + +use core::mem::MaybeUninit; +use core::slice; + +impl FromIterator for SizedSlice { + #[inline] + fn from_iter>(iter: I) -> Self { + let mut iter = iter.into_iter(); + + let mut buf: [MaybeUninit; N] = unsafe { MaybeUninit::uninit().assume_init() }; + let mut len = 0x0; + + for item in &mut buf { + let Some(value) = iter.next() else { break }; + item.write(value); + + len += 0x1; + } + + Self { buf, len } + } +} + +impl IntoIterator for SizedSlice { + type Item = T; + + type IntoIter = SizedIter; + + #[inline(always)] + fn into_iter(self) -> Self::IntoIter { + let Self { buf, len } = self; + + unsafe { SizedIter::new(buf, len) } + } +} + +impl<'a, T, const N: usize> IntoIterator for &'a SizedSlice { + type Item = &'a T; + + type IntoIter = slice::Iter<'a, T>; + + #[inline(always)] + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a, T, const N: usize> IntoIterator for &'a mut SizedSlice { + type Item = &'a mut T; + + type IntoIter = slice::IterMut<'a, T>; + + #[inline(always)] + fn into_iter(self) -> Self::IntoIter { + self.iter_mut() + } +} diff --git a/librum/src/sized_slice/mod.rs b/librum/src/sized_slice/mod.rs new file mode 100644 index 0000000..47e326d --- /dev/null +++ b/librum/src/sized_slice/mod.rs @@ -0,0 +1,277 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Librum. +// +// Librum 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. +// +// Librum 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 Librum. If +// not, see . + +#[cfg(test)] +mod tests; + +use crate::error::SizeError; + +use core::fmt::{self, Debug, Formatter}; +use core::hash::{Hash, Hasher}; +use core::mem::MaybeUninit; +use core::ops::{Index, IndexMut}; +use core::ptr::{copy_nonoverlapping, null, null_mut}; +use core::slice::SliceIndex; + +// Conversion facilities: +mod conv; + +// Comparison facilities: +mod cmp; + +// Iterator facilities: +mod iter; + +// Encode/decode facilities: +mod serdes; + +/// Stack-allocated vector with maximum length. +/// +/// This type is intended as a [sized-encodable](crate::SizedEncode) alternative to [`Vec`](alloc::vec::Vec) -- for cases where [arrays](array) may not be wanted -- as well as a [decodable](crate::Decode) alternative to normal [slices](slice). +/// +/// Note that this type is immutable in the sense that it does **not** define methods like `push` and `pop`, unlike `Vec`. +/// +/// See [`SizedStr`](crate::SizedStr) for an equivalent alternative to [`String`](alloc::string::String). +/// +/// # Examples +/// +/// All instances of this type with the same `T` and `N` also have the exact same layout: +/// +/// ``` +/// use librum::SizedSlice; +/// +/// let vec0 = SizedSlice::::try_from([0x3].as_slice()).unwrap(); +/// let vec1 = SizedSlice::::try_from([0x3, 0x2].as_slice()).unwrap(); +/// let vec2 = SizedSlice::::try_from([0x3, 0x2, 0x4].as_slice()).unwrap(); +/// let vec3 = SizedSlice::::try_from([0x3, 0x2, 0x4, 0x3].as_slice()).unwrap(); +/// +/// assert_eq!(size_of_val(&vec0), size_of_val(&vec1)); +/// assert_eq!(size_of_val(&vec0), size_of_val(&vec2)); +/// assert_eq!(size_of_val(&vec0), size_of_val(&vec3)); +/// assert_eq!(size_of_val(&vec1), size_of_val(&vec2)); +/// assert_eq!(size_of_val(&vec1), size_of_val(&vec3)); +/// assert_eq!(size_of_val(&vec2), size_of_val(&vec3)); +/// ``` +pub struct SizedSlice { + buf: [MaybeUninit; N], + len: usize, +} + +impl SizedSlice { + /// Copies elements from a slice. + #[inline] + pub const fn copy_from_slice(&mut self, data: &[T]) + where + T: Copy, + { + unsafe { + let src = data.as_ptr(); + let dst = self.buf.as_mut_ptr().cast(); + let count = data.len(); + + // SAFETY: `T` implements `Copy`. + copy_nonoverlapping(src, dst, count); + + self.set_len(count); + } + } + + /// Generates a sized slice referencing the elements of `self`. + #[inline] + pub const fn each_ref(&self) -> SizedSlice<&T, N> { + let mut buf = [null::(); N]; + let len = self.len; + + let mut i = 0x0; + while i < len { + unsafe { + let item = buf.as_mut_ptr().add(i); + + let value = self.as_ptr().add(i).cast(); + item.write(value); + } + + i += 0x1; + } + + // SAFETY: `*const T` has the same layout as + // `MaybeUninit<&T>`, and every relavent pointer + // has been initialised as a valid reference. + let buf = unsafe { (&raw const buf).cast::<[MaybeUninit<&T>; N]>().read() }; + + unsafe { SizedSlice::from_raw_parts(buf, len) } + } + + /// Generates a sized slice mutably referencing the elements of `self`. + #[inline] + pub const fn each_mut(&mut self) -> SizedSlice<&mut T, N> { + let mut buf = [null_mut::(); N]; + let len = self.len; + + let mut i = 0x0; + while i < len { + unsafe { + let item = buf.as_mut_ptr().add(i); + + let value = self.as_mut_ptr().add(i).cast(); + item.write(value); + } + + i += 0x1; + } + + // SAFETY: `*mut T` has the same layout as + // `MaybeUninit<&mut T>`, and every relavent point- + // er has been initialised as a valid reference. + let buf = unsafe { (&raw const buf).cast::<[MaybeUninit<&mut T>; N]>().read() }; + + unsafe { SizedSlice::from_raw_parts(buf, len) } + } + + /// Sets the length of the vector. + /// + /// The provided length is not tested in any way. + /// + /// # Safety + /// + /// The new length `len` may not be larger than `N`. + /// + /// It is only valid to enlarge vectors if `T` supports being in a purely uninitialised state. + /// Such is permitted with e.g. [`MaybeUninit`]. + #[inline(always)] + pub const unsafe fn set_len(&mut self, len: usize) { + debug_assert!(len <= N, "cannot set length past bounds"); + + self.len = len + } + + /// Returns the total capacity of the vector. + /// + /// By definition, this is always exactly equal to the value of `N`. + #[inline(always)] + #[must_use] + pub const fn capacity(&self) -> usize { + N + } + + /// Returns the length of the vector. + /// + /// This value may necessarily be smaller than `N`. + #[inline(always)] + #[must_use] + pub const fn len(&self) -> usize { + self.len + } + + /// Checks if the vector is empty, i.e. no elements are recorded. + /// + /// Note that the internal buffer may still contain objects that have been "shadowed" by setting a smaller length with [`len`](Self::len). + #[inline(always)] + #[must_use] + pub const fn is_empty(&self) -> bool { + self.len() == 0x0 + } + + /// Checks if the vector is full, i.e. it cannot hold any more elements. + #[inline(always)] + #[must_use] + pub const fn is_full(&self) -> bool { + self.len() == self.capacity() + } +} + +impl SizedSlice { + /// Constructs an empty, fixed-size vector. + #[inline] + pub fn new(data: &[T]) -> Result { + let mut buf: [MaybeUninit; N] = unsafe { MaybeUninit::uninit().assume_init() }; + + let len = data.len(); + if len > N { return Err(SizeError { cap: N, len }) }; + + for (item, value) in buf.iter_mut().zip(data.iter()) { + item.write(value.clone()); + } + + Ok(Self { buf, len }) + } +} + +impl Clone for SizedSlice { + #[inline] + fn clone(&self) -> Self { + unsafe { + let mut buf: [MaybeUninit; N] = MaybeUninit::uninit().assume_init(); + + for i in 0x0..self.len() { + let value = self.get_unchecked(i).clone(); + buf.get_unchecked_mut(i).write(value); + } + + Self { buf, len: self.len } + } + } +} + +impl Debug for SizedSlice { + #[inline(always)] + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + Debug::fmt(self.as_slice(), f) + } +} + +impl Default for SizedSlice { + #[inline(always)] + fn default() -> Self { + unsafe { + // SAFETY: Always safe. + let buf = MaybeUninit::uninit().assume_init(); + + // SAFETY: The resulting slice is zero lengthed. + Self::from_raw_parts(buf, 0x0) + } + } +} + +impl Hash for SizedSlice { + #[inline(always)] + fn hash(&self, state: &mut H) { + for v in self { + v.hash(state); + } + } +} + +impl, const N: usize> Index for SizedSlice { + type Output = I::Output; + + #[inline(always)] + fn index(&self, index: I) -> &Self::Output { + self.get(index).unwrap() + } +} + +impl, const N: usize> IndexMut for SizedSlice { + #[inline(always)] + fn index_mut(&mut self, index: I) -> &mut Self::Output { + self.get_mut(index).unwrap() + } +} diff --git a/librum/src/sized_slice/serdes.rs b/librum/src/sized_slice/serdes.rs new file mode 100644 index 0000000..cd7ce13 --- /dev/null +++ b/librum/src/sized_slice/serdes.rs @@ -0,0 +1,69 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Librum. +// +// Librum 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. +// +// Librum 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 Librum. If +// not, see . + +use crate::{ + Decode, + DecodeBorrowed, + Encode, + IStream, + OStream, + SizedEncode, + SizedSlice +}; +use crate::error::{CollectionDecodeError, ItemDecodeError, SizeError}; + +use core::mem::MaybeUninit; + +impl Decode for SizedSlice { + type Error = CollectionDecodeError>; + + #[inline] + fn decode(stream: &mut IStream) -> Result { + let len = Decode::decode(stream).unwrap(); + if len > N { return Err(CollectionDecodeError::Length(SizeError { cap: N, len })) }; + + let mut buf: [MaybeUninit; N] = unsafe { MaybeUninit::uninit().assume_init() }; + + for (i, slot) in buf.iter_mut().enumerate() { + let v = Decode::decode(stream) + .map_err(|e| CollectionDecodeError::Item(ItemDecodeError { index: i, error: e }))?; + + slot.write(v); + } + + Ok(Self { buf, len }) + } +} + +impl DecodeBorrowed<[T]> for SizedSlice { } + +impl Encode for SizedSlice { + type Error = <[T] as Encode>::Error; + + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { + self.as_slice().encode(stream) + } +} + +impl SizedEncode for SizedSlice { + const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE * N; +} \ No newline at end of file diff --git a/bzipper/src/sized_slice/test.rs b/librum/src/sized_slice/tests.rs similarity index 75% rename from bzipper/src/sized_slice/test.rs rename to librum/src/sized_slice/tests.rs index 317b1a4..87ffe0e 100644 --- a/bzipper/src/sized_slice/test.rs +++ b/librum/src/sized_slice/tests.rs @@ -1,26 +1,26 @@ // Copyright 2024 Gabriel Bjørnager Jensen. // -// This file is part of bZipper. +// This file is part of Librum. // -// bZipper is free software: you can redistribute -// it and/or modify it under the terms of the GNU +// Librum 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 +// Librum 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 +// er General Public License along with Librum. If // not, see . -use alloc::vec::Vec; -use bzipper::SizedSlice; +use librum::SizedSlice; +use std::vec::Vec; #[test] fn test_sized_slice_from_iter() { diff --git a/librum/src/sized_str/cmp.rs b/librum/src/sized_str/cmp.rs new file mode 100644 index 0000000..5af93a9 --- /dev/null +++ b/librum/src/sized_str/cmp.rs @@ -0,0 +1,80 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Librum. +// +// Librum 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. +// +// Librum 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 Librum. If +// not, see . + +use crate::SizedStr; + +use core::cmp::Ordering; + +#[cfg(feature = "alloc")] +use alloc::string::String; + +impl Ord for SizedStr { + #[inline(always)] + fn cmp(&self, other: &Self) -> Ordering { + self.as_str().cmp(other.as_str()) + } +} + +impl PartialEq> for SizedStr { + #[inline(always)] + fn eq(&self, other: &SizedStr) -> bool { + self.as_str() == other.as_str() + } +} + +impl PartialEq<&str> for SizedStr { + #[inline(always)] + fn eq(&self, other: &&str) -> bool { + self.as_str() == *other + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl PartialEq for SizedStr { + #[inline(always)] + fn eq(&self, other: &String) -> bool { + self.as_str() == other.as_str() + } +} + +impl PartialOrd> for SizedStr { + #[inline(always)] + fn partial_cmp(&self, other: &SizedStr) -> Option { + self.as_str().partial_cmp(other.as_str()) + } +} + +impl PartialOrd<&str> for SizedStr { + #[inline(always)] + fn partial_cmp(&self, other: &&str) -> Option { + self.as_str().partial_cmp(*other) + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(doc, doc(cfg(feature = "alloc")))] +impl PartialOrd for SizedStr { + #[inline(always)] + fn partial_cmp(&self, other: &String) -> Option { + self.as_str().partial_cmp(other.as_str()) + } +} diff --git a/bzipper/src/sized_str/mod.rs b/librum/src/sized_str/conv.rs similarity index 51% rename from bzipper/src/sized_str/mod.rs rename to librum/src/sized_str/conv.rs index fd1deac..38bba9b 100644 --- a/bzipper/src/sized_str/mod.rs +++ b/librum/src/sized_str/conv.rs @@ -1,54 +1,33 @@ // Copyright 2024 Gabriel Bjørnager Jensen. // -// This file is part of bZipper. +// This file is part of Librum. // -// bZipper is free software: you can redistribute -// it and/or modify it under the terms of the GNU +// Librum 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 +// Librum 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 +// er General Public License along with Librum. If // not, see . -#[cfg(test)] -mod test; - -use crate::{ - Decode, - Encode, - IStream, - OStream, - SizedEncode, - SizedSlice, -}; -use crate::error::{ - DecodeError, - EncodeError, - SizeError, - StringError, - Utf8Error, -}; +use crate::{SizedSlice, SizedStr}; +use crate::error::{SizeError, StringError, Utf8Error}; use core::borrow::{Borrow, BorrowMut}; -use core::cmp::Ordering; -use core::fmt::{self, Debug, Display, Formatter}; -use core::hash::{Hash, Hasher}; use core::mem::{ManuallyDrop, MaybeUninit}; -use core::ops::{Deref, DerefMut, Index, IndexMut}; -use core::ptr::{addr_of, copy_nonoverlapping}; +use core::ops::{Deref, DerefMut}; +use core::ptr::copy_nonoverlapping; use core::slice; -use core::slice::SliceIndex; -use core::str; -use core::str::{Chars, CharIndices, FromStr}; +use core::str::{self, FromStr}; #[cfg(feature = "alloc")] use alloc::boxed::Box; @@ -65,62 +44,7 @@ use std::net::ToSocketAddrs; #[cfg(feature = "std")] use std::path::Path; -/// Stack-allocated string with maximum length. -/// -/// This is in contrast to [`String`] -- which has no size limit in practice -- and [`prim@str`], which is unsized. -/// -/// The string itself is encoded in UTF-8 for interoperability wtih Rust's standard string facilities, and partly due to memory concerns. -/// -/// Keep in mind that the size limit specified by `N` denotes *bytes* (octets) and **not** *characters* -- i.e. a value of `8` may translate to between two and eight characters due to variable-length encoding. -/// -/// See [`SizedSlice`] for an equivalent alternative to [`Vec`](alloc::vec::Vec). -/// -/// # Examples -/// -/// All instances of this type have the same size if the value of `N` is also the same. -/// Therefore, the following four strings have -- despite their different contents -- the same total size. -/// -/// ``` -/// use bzipper::SizedStr; -/// use std::str::FromStr; -/// -/// let str0 = SizedStr::<0x40>::default(); // Empty string. -/// let str1 = SizedStr::<0x40>::from_str("Hello there!").unwrap(); -/// let str2 = SizedStr::<0x40>::from_str("أنا من أوروپا").unwrap(); -/// let str3 = SizedStr::<0x40>::from_str("COGITO ERGO SUM").unwrap(); -/// -/// assert_eq!(size_of_val(&str0), size_of_val(&str1)); -/// assert_eq!(size_of_val(&str0), size_of_val(&str2)); -/// assert_eq!(size_of_val(&str0), size_of_val(&str3)); -/// assert_eq!(size_of_val(&str1), size_of_val(&str2)); -/// assert_eq!(size_of_val(&str1), size_of_val(&str3)); -/// assert_eq!(size_of_val(&str2), size_of_val(&str3)); -/// ``` -/// -/// These three strings can -- by extend in theory -- also interchange their contents between each other. -#[derive(Clone, Default)] -pub struct SizedStr(SizedSlice); - impl SizedStr { - /// Constructs an empty, fixed-size string. - /// - /// Note that string is not required to completely fill out its size-constraint. - /// - /// The constructed string will have a null length. - /// - /// For constructing a string with an already defined buffer, see [`from_raw_parts`](Self::from_raw_parts) and [`from_str`](Self::from_str). - /// - /// # Errors - /// - /// If the internal buffer cannot contain the entirety of `s`, then an error is returned. - #[inline(always)] - pub const fn new(s: &str) -> Result { - if s.len() > N { return Err(StringError::SmallBuffer(SizeError { req: s.len(), len: N })) }; - - let this = unsafe { Self::from_utf8_unchecked(s.as_bytes()) }; - Ok(this) - } - /// Constructs a fixed-size string from UTF-8 octets. /// /// The passed slice is checked for its validity. @@ -131,7 +55,7 @@ impl SizedStr { /// Each byte value must be a valid UTF-8 code point. #[inline] pub const fn from_utf8(data: &[u8]) -> Result { - if data.len() > N { return Err(StringError::SmallBuffer(SizeError { req: data.len(), len: N })) }; + if data.len() > N { return Err(StringError::SmallBuffer(SizeError { cap: N, len: data.len() })) }; let s = match str::from_utf8(data) { Ok(s) => s, @@ -144,8 +68,8 @@ impl SizedStr { } }; - // SAFETY: `s` is guaranteed to only contain valid - // octets. + // SAFETY: `s` has been tested to only contain + // valid octets. let this = unsafe { Self::from_utf8_unchecked(s.as_bytes()) }; Ok(this) } @@ -159,7 +83,6 @@ impl SizedStr { #[inline] #[must_use] pub const unsafe fn from_utf8_unchecked(s: &[u8]) -> Self { - // Should we assert the length? debug_assert!(s.len() <= N, "cannot construct string from utf-8 sequence that is longer"); let mut buf = [0x00; N]; @@ -186,8 +109,7 @@ impl SizedStr { pub const unsafe fn from_raw_parts(buf: [u8; N], len: usize) -> Self { debug_assert!(len <= N, "cannot construct string that is longer than its capacity"); - let init_buf = ManuallyDrop::new(buf); - let buf = unsafe { addr_of!(init_buf).cast::<[MaybeUninit; N]>().read() }; + let buf = unsafe { buf.as_ptr().cast::<[MaybeUninit; N]>().read() }; Self(SizedSlice::from_raw_parts(buf, len)) } @@ -231,6 +153,8 @@ impl SizedStr { #[inline(always)] #[must_use] pub const fn as_str(&self) -> &str { + // SAFETY: We guarantee that all octets are valid + // UTF-8 code points. unsafe { core::str::from_utf8_unchecked(self.as_bytes()) } } @@ -244,55 +168,11 @@ impl SizedStr { let ptr = self.as_mut_ptr(); let len = self.len(); - let bytes = slice::from_raw_parts_mut(ptr, len); - core::str::from_utf8_unchecked_mut(bytes) + let data = slice::from_raw_parts_mut(ptr, len); + core::str::from_utf8_unchecked_mut(data) } } - /// Returns the total capacity of the string. - /// - /// This is defined as being exactly the value of `N`. - #[inline(always)] - #[must_use] - pub const fn capacity(&self) -> usize { - self.0.capacity() - } - - /// Returns the length of the string. - /// - /// This does not necessarily equate to the value of `N`, as the internal buffer may be used but partially. - #[inline(always)] - #[must_use] - pub const fn len(&self) -> usize { - self.0.len() - } - - /// Checks if the string is empty, i.e. no characters are contained. - #[inline(always)] - #[must_use] - pub const fn is_empty(&self) -> bool { - self.0.is_empty() - } - - /// Checks if the string is full, i.e. it cannot hold any more characters. - #[inline(always)] - #[must_use] - pub const fn is_full(&self) -> bool { - self.0.is_full() - } - - /// Returns an iterator of the string's characters. - #[inline(always)] - pub fn chars(&self) -> Chars { - self.as_str().chars() - } - - /// Returns an iterator of the string's characters along with their positions. - #[inline(always)] - pub fn char_indices(&self) -> CharIndices { - self.as_str().char_indices() - } - /// Destructs the provided string into its raw parts. /// /// The returned values are valid to pass on to [`from_raw_parts`](Self::from_raw_parts). @@ -306,7 +186,7 @@ impl SizedStr { let (buf, len) = vec.into_raw_parts(); let init_buf = ManuallyDrop::new(buf); - let buf = unsafe { addr_of!(init_buf).cast::<[u8; N]>().read() }; + let buf = unsafe { (&raw const init_buf).cast::<[u8; N]>().read() }; (buf, len) } @@ -394,13 +274,6 @@ impl BorrowMut for SizedStr { } } -impl Debug for SizedStr { - #[inline] - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - Debug::fmt(self.as_str(), f) - } -} - impl Deref for SizedStr { type Target = str; @@ -417,162 +290,18 @@ impl DerefMut for SizedStr { } } -impl Decode for SizedStr { - #[inline] - fn decode(stream: &mut IStream) -> Result { - let len = Decode::decode(stream)?; - - let data = stream.read(len); - - Self::from_utf8(data) - .map_err(|e| match e { - StringError::BadUtf8(e) => DecodeError::BadString(e), - - StringError::SmallBuffer(e) => DecodeError::SmallBuffer(e), - - _ => unreachable!(), - }) - } -} - -impl Display for SizedStr { - #[inline] - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - Display::fmt(self.as_str(), f) - } -} - -impl Encode for SizedStr { - #[inline(always)] - fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> { - // Optimised encode. Don't just rely on `SizedSlice`. - - self.as_str().encode(stream) - } -} - -impl Eq for SizedStr { } - -impl FromIterator for SizedStr { - #[inline] - fn from_iter>(iter: I) -> Self { - let mut buf = [0x00; N]; - let mut len = 0x0; - - for c in iter { - let rem = N - len; - let req = c.len_utf8(); - - if rem < req { break } - - let start = len; - let stop = start + req; - - c.encode_utf8(&mut buf[start..stop]); - - len += req; - } - - // SAFETY: All octets are initialised and come from - // `char::encode_utf8`. - unsafe { Self::from_raw_parts(buf, len) } - } -} - impl FromStr for SizedStr { type Err = StringError; #[inline] fn from_str(s: &str) -> Result { - if s.len() > N { return Err(StringError::SmallBuffer(SizeError { req: s.len(), len: N })) }; + if s.len() > N { return Err(StringError::SmallBuffer(SizeError { cap: N, len: s.len() })) }; let this = unsafe { Self::from_utf8_unchecked(s.as_bytes()) }; Ok(this) } } -impl Hash for SizedStr { - #[inline(always)] - fn hash(&self, state: &mut H) { - self.as_str().hash(state) - } -} - -impl, const N: usize> Index for SizedStr { - type Output = I::Output; - - #[inline(always)] - fn index(&self, index: I) -> &Self::Output { - self.get(index).unwrap() - } -} - -impl, const N: usize> IndexMut for SizedStr { - #[inline(always)] - fn index_mut(&mut self, index: I) -> &mut Self::Output { - self.get_mut(index).unwrap() - } -} - -impl Ord for SizedStr { - #[inline(always)] - fn cmp(&self, other: &Self) -> Ordering { - self.as_str().cmp(other.as_str()) - } -} - -impl PartialEq> for SizedStr { - #[inline(always)] - fn eq(&self, other: &SizedStr) -> bool { - self.as_str() == other.as_str() - } -} - -impl PartialEq<&str> for SizedStr { - #[inline(always)] - fn eq(&self, other: &&str) -> bool { - self.as_str() == *other - } -} - -#[cfg(feature = "alloc")] -#[cfg_attr(doc, doc(cfg(feature = "alloc")))] -impl PartialEq for SizedStr { - #[inline(always)] - fn eq(&self, other: &String) -> bool { - self.as_str() == other.as_str() - } -} - -impl PartialOrd> for SizedStr { - #[inline(always)] - fn partial_cmp(&self, other: &SizedStr) -> Option { - self.as_str().partial_cmp(other.as_str()) - } -} - -impl PartialOrd<&str> for SizedStr { - #[inline(always)] - fn partial_cmp(&self, other: &&str) -> Option { - self.as_str().partial_cmp(*other) - } -} - -#[cfg(feature = "alloc")] -#[cfg_attr(doc, doc(cfg(feature = "alloc")))] -impl PartialOrd for SizedStr { - #[inline(always)] - fn partial_cmp(&self, other: &String) -> Option { - self.as_str().partial_cmp(other.as_str()) - } -} - -unsafe impl SizedEncode for SizedStr { - const MAX_ENCODED_SIZE: usize = - usize::MAX_ENCODED_SIZE - + SizedSlice::::MAX_ENCODED_SIZE; -} - #[cfg(feature = "std")] #[cfg_attr(doc, doc(cfg(feature = "std")))] impl ToSocketAddrs for SizedStr { diff --git a/librum/src/sized_str/mod.rs b/librum/src/sized_str/mod.rs new file mode 100644 index 0000000..ca5ee3e --- /dev/null +++ b/librum/src/sized_str/mod.rs @@ -0,0 +1,204 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Librum. +// +// Librum 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. +// +// Librum 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 Librum. If +// not, see . + +#[cfg(test)] +mod tests; + +use crate::SizedSlice; +use crate::error::{SizeError, StringError}; + +use core::fmt::{self, Debug, Display, Formatter}; +use core::hash::{Hash, Hasher}; +use core::ops::{Index, IndexMut}; +use core::slice::SliceIndex; +use core::str::{Chars, CharIndices}; + +// Comparison facilities: +mod cmp; + +// Conversion facilities: +mod conv; + +// Encode/decode facilities: +mod serdes; + +/// Stack-allocated string with maximum length. +/// +/// This is in contrast to [`String`](alloc::string::String) -- which has no size limit in practice -- and [`prim@str`], which is unsized. +/// +/// The string itself is encoded in UTF-8 for interoperability wtih Rust's standard string facilities, and partly due to memory concerns. +/// +/// Keep in mind that the size limit specified by `N` denotes *bytes* (octets) and **not** *characters* -- i.e. a value of `8` may translate to between two and eight characters due to variable-length encoding. +/// +/// See [`SizedSlice`] for an equivalent alternative to [`Vec`](alloc::vec::Vec). +/// +/// # Examples +/// +/// All instances of this type have the same size if the value of `N` is also the same. +/// Therefore, the following four strings have -- despite their different contents -- the same total size. +/// +/// ``` +/// use librum::SizedStr; +/// use std::str::FromStr; +/// +/// let str0 = SizedStr::<0x40>::default(); // Empty string. +/// let str1 = SizedStr::<0x40>::from_str("Hello there!").unwrap(); +/// let str2 = SizedStr::<0x40>::from_str("أنا من أوروپا").unwrap(); +/// let str3 = SizedStr::<0x40>::from_str("COGITO ERGO SUM").unwrap(); +/// +/// assert_eq!(size_of_val(&str0), size_of_val(&str1)); +/// assert_eq!(size_of_val(&str0), size_of_val(&str2)); +/// assert_eq!(size_of_val(&str0), size_of_val(&str3)); +/// assert_eq!(size_of_val(&str1), size_of_val(&str2)); +/// assert_eq!(size_of_val(&str1), size_of_val(&str3)); +/// assert_eq!(size_of_val(&str2), size_of_val(&str3)); +/// ``` +/// +/// These three strings can -- by extend in theory -- also interchange their contents between each other. +#[derive(Clone, Default)] +pub struct SizedStr(SizedSlice); + +impl SizedStr { + /// Constructs a new, fixed-size string. + /// + /// Note that string is not required to completely fill out its size-constraint. + /// + /// # Errors + /// + /// If the internal buffer cannot contain the entirety of `s`, then an error is returned. + #[inline(always)] + pub const fn new(s: &str) -> Result { + if s.len() > N { return Err(StringError::SmallBuffer(SizeError { cap: N, len: s.len() })) }; + + let this = unsafe { Self::from_utf8_unchecked(s.as_bytes()) }; + Ok(this) + } + + /// Returns the total capacity of the string. + /// + /// This is defined as being exactly the value of `N`. + #[inline(always)] + #[must_use] + pub const fn capacity(&self) -> usize { + self.0.capacity() + } + + /// Returns the length of the string. + /// + /// This does not necessarily equate to the value of `N`, as the internal buffer may be used but partially. + #[inline(always)] + #[must_use] + pub const fn len(&self) -> usize { + self.0.len() + } + + /// Checks if the string is empty, i.e. no characters are contained. + #[inline(always)] + #[must_use] + pub const fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Checks if the string is full, i.e. it cannot hold any more characters. + #[inline(always)] + #[must_use] + pub const fn is_full(&self) -> bool { + self.0.is_full() + } + + /// Returns an iterator of the string's characters. + #[inline(always)] + pub fn chars(&self) -> Chars { + self.as_str().chars() + } + + /// Returns an iterator of the string's characters along with their positions. + #[inline(always)] + pub fn char_indices(&self) -> CharIndices { + self.as_str().char_indices() + } +} + +impl Debug for SizedStr { + #[inline(always)] + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + Debug::fmt(self.as_str(), f) + } +} + +impl Display for SizedStr { + #[inline(always)] + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + Display::fmt(self.as_str(), f) + } +} + +impl Eq for SizedStr { } + +impl FromIterator for SizedStr { + #[inline] + fn from_iter>(iter: I) -> Self { + let mut buf = [0x00; N]; + let mut len = 0x0; + + for c in iter { + let rem = N - len; + let req = c.len_utf8(); + + if rem < req { break } + + let start = len; + let stop = start + req; + + c.encode_utf8(&mut buf[start..stop]); + + len += req; + } + + // SAFETY: All octets are initialised and come from + // `char::encode_utf8`. + unsafe { Self::from_raw_parts(buf, len) } + } +} + +impl Hash for SizedStr { + #[inline(always)] + fn hash(&self, state: &mut H) { + self.as_str().hash(state) + } +} + +impl, const N: usize> Index for SizedStr { + type Output = I::Output; + + #[inline(always)] + fn index(&self, index: I) -> &Self::Output { + self.get(index).unwrap() + } +} + +impl, const N: usize> IndexMut for SizedStr { + #[inline(always)] + fn index_mut(&mut self, index: I) -> &mut Self::Output { + self.get_mut(index).unwrap() + } +} + diff --git a/librum/src/sized_str/serdes.rs b/librum/src/sized_str/serdes.rs new file mode 100644 index 0000000..1a61e0d --- /dev/null +++ b/librum/src/sized_str/serdes.rs @@ -0,0 +1,68 @@ +// Copyright 2024 Gabriel Bjørnager Jensen. +// +// This file is part of Librum. +// +// Librum 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. +// +// Librum 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 Librum. If +// not, see . + +use crate::{ + Decode, + DecodeBorrowed, + Encode, + IStream, + OStream, + SizedEncode, + SizedStr +}; +use crate::error::{CollectionDecodeError, SizeError, StringError, Utf8Error}; + +impl Decode for SizedStr { + type Error = CollectionDecodeError; + + #[inline] + fn decode(stream: &mut IStream) -> Result { + let len = Decode::decode(stream).unwrap(); + + let data = stream.read(len); + + Self::from_utf8(data) + .map_err(|e| match e { + StringError::BadUtf8(e) => CollectionDecodeError::Item(e), + + StringError::SmallBuffer(e) => CollectionDecodeError::Length(e), + + _ => unreachable!(), + }) + } +} + +impl DecodeBorrowed for SizedStr { } + +impl Encode for SizedStr { + type Error = ::Error; + + #[inline(always)] + fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> { + self.as_str().encode(stream) + } +} + +impl SizedEncode for SizedStr { + const MAX_ENCODED_SIZE: usize = + usize::MAX_ENCODED_SIZE + + u8::MAX_ENCODED_SIZE * N; +} diff --git a/bzipper/src/sized_str/test.rs b/librum/src/sized_str/tests.rs similarity index 87% rename from bzipper/src/sized_str/test.rs rename to librum/src/sized_str/tests.rs index bfe0e7b..7012642 100644 --- a/bzipper/src/sized_str/test.rs +++ b/librum/src/sized_str/tests.rs @@ -1,27 +1,27 @@ // Copyright 2024 Gabriel Bjørnager Jensen. // -// This file is part of bZipper. +// This file is part of Librum. // -// bZipper is free software: you can redistribute -// it and/or modify it under the terms of the GNU +// Librum 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 +// Librum 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 +// er General Public License along with Librum. If // not, see . -use bzipper::SizedStr; -use bzipper::error::{StringError, Utf8Error}; -use core::cmp::Ordering; +use librum::SizedStr; +use librum::error::{StringError, Utf8Error}; +use std::cmp::Ordering; #[test] fn test_fixed_str_from_iter() {