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';

This commit is contained in:
Gabriel Bjørnager Jensen 2024-11-24 12:49:13 +01:00
parent 74b8d2caa2
commit 0160d295bd
78 changed files with 4630 additions and 2540 deletions

View file

@ -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`

View file

@ -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"

140
README.md
View file

@ -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

View file

@ -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 <https://www.gnu.org/licenses/>.
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<dyn core::error::Error>),
/// 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,
}
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
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<dyn core::error::Error>),
/// 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,
}
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
//! 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);

View file

@ -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 <https://www.gnu.org/licenses/>.
#[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::<u8, 0x4>::try_from([0x3].as_slice()).unwrap();
/// let vec1 = SizedSlice::<u8, 0x4>::try_from([0x3, 0x2].as_slice()).unwrap();
/// let vec2 = SizedSlice::<u8, 0x4>::try_from([0x3, 0x2, 0x4].as_slice()).unwrap();
/// let vec3 = SizedSlice::<u8, 0x4>::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<T, const N: usize> {
buf: [MaybeUninit<T>; N],
len: usize,
}
impl<T, const N: usize> SizedSlice<T, N> {
/// 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<T>; 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<T>; 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::<T>(len).unwrap();
let ptr = alloc(layout).cast::<T>();
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<T> {
self.into_boxed_slice().into_vec()
}
}
impl<T: Clone, const N: usize> SizedSlice<T, N> {
/// Constructs an empty, fixed-size vector.
#[inline]
pub fn new(data: &[T]) -> Result<Self, SizeError> {
let mut buf: [MaybeUninit<T>; 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<T, const N: usize> AsMut<[T]> for SizedSlice<T, N> {
#[inline(always)]
fn as_mut(&mut self) -> &mut [T] {
self.as_mut_slice()
}
}
impl<T, const N: usize> AsRef<[T]> for SizedSlice<T, N> {
#[inline(always)]
fn as_ref(&self) -> &[T] {
self.as_slice()
}
}
impl<T, const N: usize> Borrow<[T]> for SizedSlice<T, N> {
#[inline(always)]
fn borrow(&self) -> &[T] {
self.as_slice()
}
}
impl<T, const N: usize> BorrowMut<[T]> for SizedSlice<T, N> {
#[inline(always)]
fn borrow_mut(&mut self) -> &mut [T] {
self.as_mut_slice()
}
}
impl<T: Clone, const N: usize> Clone for SizedSlice<T, N> {
#[inline]
fn clone(&self) -> Self {
unsafe {
let mut buf: [MaybeUninit<T>; 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<T: Debug, const N: usize> Debug for SizedSlice<T, N> {
#[inline(always)]
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Debug::fmt(self.as_slice(), f)
}
}
impl<T, const N: usize> Default for SizedSlice<T, N> {
#[inline(always)]
fn default() -> Self {
Self { buf: unsafe { MaybeUninit::uninit().assume_init() }, len: 0x0 }
}
}
impl<T, const N: usize> Deref for SizedSlice<T, N> {
type Target = [T];
#[inline(always)]
fn deref(&self) -> &Self::Target {
self.as_slice()
}
}
impl<T, const N: usize> DerefMut for SizedSlice<T, N> {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target {
self.as_mut_slice()
}
}
impl<T: Decode, const N: usize> Decode for SizedSlice<T, N> {
#[inline]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
let len = Decode::decode(stream)?;
if len > N { return Err(DecodeError::SmallBuffer(SizeError { req: len, len: N })) };
let mut buf: [MaybeUninit<T>; N] = unsafe { MaybeUninit::uninit().assume_init() };
for item in &mut buf {
let value = Decode::decode(stream)?;
item.write(value);
}
Ok(Self { buf, len })
}
}
impl<T: Encode, const N: usize> Encode for SizedSlice<T, N> {
#[inline(always)]
fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> {
self.as_slice().encode(stream)
}
}
impl<T: Eq, const N: usize> Eq for SizedSlice<T, N> { }
impl<T, const N: usize> From<[T; N]> for SizedSlice<T, N> {
#[inline(always)]
fn from(value: [T; N]) -> Self {
unsafe {
let buf = value.as_ptr().cast::<[MaybeUninit<T>; N]>().read();
Self { buf, len: N }
}
}
}
impl<T, const N: usize> FromIterator<T> for SizedSlice<T, N> {
#[inline]
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
let mut iter = iter.into_iter();
let mut buf: [MaybeUninit<T>; 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<T: Hash, const N: usize> Hash for SizedSlice<T, N> {
#[inline(always)]
fn hash<H: Hasher>(&self, state: &mut H) {
for v in self {
v.hash(state);
}
}
}
impl<T, I: SliceIndex<[T]>, const N: usize> Index<I> for SizedSlice<T, N> {
type Output = I::Output;
#[inline(always)]
fn index(&self, index: I) -> &Self::Output {
self.get(index).unwrap()
}
}
impl<T, I: SliceIndex<[T]>, const N: usize> IndexMut<I> for SizedSlice<T, N> {
#[inline(always)]
fn index_mut(&mut self, index: I) -> &mut Self::Output {
self.get_mut(index).unwrap()
}
}
impl<T, const N: usize> IntoIterator for SizedSlice<T, N> {
type Item = T;
type IntoIter = SizedIter<T, N>;
#[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<T, N> {
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<T, N> {
type Item = &'a mut T;
type IntoIter = IterMut<'a, T>;
#[inline(always)]
fn into_iter(self) -> Self::IntoIter {
self.iter_mut()
}
}
impl<T: Ord, const N: usize> Ord for SizedSlice<T, N> {
#[inline(always)]
fn cmp(&self, other: &Self) -> Ordering {
self.as_slice().cmp(other.as_slice())
}
}
impl<T: PartialEq<U>, U: PartialEq<T>, const N: usize, const M: usize> PartialEq<SizedSlice<U, M>> for SizedSlice<T, N> {
#[inline(always)]
fn eq(&self, other: &SizedSlice<U, M>) -> bool {
self.as_slice() == other.as_slice()
}
}
impl<T: PartialEq<U>, U: PartialEq<T>, const N: usize, const M: usize> PartialEq<[U; M]> for SizedSlice<T, N> {
#[inline(always)]
fn eq(&self, other: &[U; M]) -> bool {
self.as_slice() == other.as_slice()
}
}
impl<T: PartialEq<U>, U: PartialEq<T>, const N: usize> PartialEq<&[U]> for SizedSlice<T, N> {
#[inline(always)]
fn eq(&self, other: &&[U]) -> bool {
self.as_slice() == *other
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<T: PartialEq<U>, U: PartialEq<T>, const N: usize> PartialEq<Vec<U>> for SizedSlice<T, N> {
#[inline(always)]
fn eq(&self, other: &Vec<U>) -> bool {
self.as_slice() == other.as_slice()
}
}
impl<T: PartialOrd, const N: usize, const M: usize> PartialOrd<SizedSlice<T, M>> for SizedSlice<T, N> {
#[inline(always)]
fn partial_cmp(&self, other: &SizedSlice<T, M>) -> Option<Ordering> {
self.as_slice().partial_cmp(other.as_slice())
}
}
impl<T: PartialOrd, const N: usize, const M: usize> PartialOrd<[T; M]> for SizedSlice<T, N> {
#[inline(always)]
fn partial_cmp(&self, other: &[T; M]) -> Option<Ordering> {
self.as_slice().partial_cmp(other.as_slice())
}
}
impl<T: PartialOrd, const N: usize> PartialOrd<&[T]> for SizedSlice<T, N> {
#[inline(always)]
fn partial_cmp(&self, other: &&[T]) -> Option<Ordering> {
self.as_slice().partial_cmp(*other)
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<T: PartialOrd, const N: usize> PartialOrd<Vec<T>> for SizedSlice<T, N> {
#[inline(always)]
fn partial_cmp(&self, other: &Vec<T>) -> Option<Ordering> {
self.as_slice().partial_cmp(other.as_slice())
}
}
unsafe impl<T: SizedEncode, const N: usize> SizedEncode for SizedSlice<T, N> {
const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE * N;
}
impl<T: Clone, const N: usize> TryFrom<&[T]> for SizedSlice<T, N> {
type Error = SizeError;
#[inline(always)]
fn try_from(value: &[T]) -> Result<Self, Self::Error> {
Self::new(value)
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<T, const N: usize> From<SizedSlice<T, N>> for Box<[T]> {
#[inline(always)]
fn from(value: SizedSlice<T, N>) -> Self {
value.into_boxed_slice()
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<T, const N: usize> From<SizedSlice<T, N>> for Vec<T> {
#[inline(always)]
fn from(value: SizedSlice<T, N>) -> Self {
value.into_vec()
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
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::<isize>()
.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);
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
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::<TokenStream, Token![,]>::new();
for (discriminant, variant) in DiscriminantIter::new(&data.variants) {
let mut chain_commands = Punctuated::<TokenStream, Token![,]>::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<Self, ::bzipper::error::DecodeError> {
let value = match (<isize as ::bzipper::Decode>::decode(stream)?) { #match_arms };
Ok(value)
}
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
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::<TokenStream, Token![,]>::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<Self, ::bzipper::error::DecodeError> {
let value = #value;
Ok(value)
}
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
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(())
}
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
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(())
}
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
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! {
<isize as ::bzipper::SizedEncode>::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
};
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
#![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()
}

View file

@ -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"] }

View file

@ -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: <https://crates.io/crates/bincode/>");
println!("- Borsh: <https://crates.io/crates/borsh/>");
println!("- Ciborium: <https://crates.io/crates/ciborium/>");
println!("- bzipper: <https://crates.io/crates/bzipper/>");
println!("- Librum: <https://crates.io/crates/librum/>");
println!("- Postcard: <https://crates.io/crates/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::<u8>(); // value
let buf_size = size_of::<u8>();
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::<u8>()).unwrap();
@ -192,21 +183,19 @@ fn main() {
}
borsh: {
use std::io::Cursor;
let buf_size = size_of::<u8>();
let buf_size = size_of::<u8>(); // 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::<u8>()).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::<u8>() // header
+ size_of::<u8>(); // value
let mut buf = Cursor::new(vec![0x00; buf_size * VALUE_COUNT]);
for _ in 0x0..VALUE_COUNT {
ciborium::into_writer(&random::<u8>(), &mut buf).unwrap();
}
}
postcard: {
use std::io::Cursor;
let buf_size = size_of::<u8>();
let buf_size = size_of::<u8>(); // 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::<u8>(), &mut buf).unwrap();
@ -243,14 +216,107 @@ fn main() {
}
}
encode_u32: {
bincode: {
use bincode::serialize_into;
let buf_size = size_of::<u32>();
let mut buf = vec![0x00; buf_size * VALUE_COUNT];
for _ in 0x0..VALUE_COUNT {
serialize_into(&mut buf, &random::<u32>()).unwrap();
}
}
borsh: {
let buf_size = size_of::<u32>();
let mut buf = vec![0x00; buf_size * VALUE_COUNT];
for _ in 0x0..VALUE_COUNT {
borsh::to_writer(&mut buf, &random::<u32>()).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::<u32>().encode(&mut stream).unwrap();
}
}
postcard: {
let buf_size = size_of::<u32>();
let mut buf = vec![0x00; buf_size * VALUE_COUNT];
for _ in 0x0..VALUE_COUNT {
postcard::to_io(&random::<u32>(), &mut buf).unwrap();
}
}
}
encode_u128: {
bincode: {
use bincode::serialize_into;
let buf_size = size_of::<u128>();
let mut buf = vec![0x00; buf_size * VALUE_COUNT];
for _ in 0x0..VALUE_COUNT {
serialize_into(&mut buf, &random::<u128>()).unwrap();
}
}
borsh: {
let buf_size = size_of::<u128>();
let mut buf = vec![0x00; buf_size * VALUE_COUNT];
for _ in 0x0..VALUE_COUNT {
borsh::to_writer(&mut buf, &random::<u128>()).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::<u128>().encode(&mut stream).unwrap();
}
}
postcard: {
let buf_size = size_of::<u128>();
let mut buf = vec![0x00; buf_size * VALUE_COUNT];
for _ in 0x0..VALUE_COUNT {
postcard::to_io(&random::<u128>(), &mut buf).unwrap();
}
}
}
encode_struct_unit: {
bincode: {
use bincode::serialize_into;
use std::io::Cursor;
let buf_size = size_of::<Unit>(); // value
let buf_size = size_of::<Unit>();
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::<Unit>();
let buf_size = size_of::<Unit>(); // 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::<u8>() // header
+ size_of::<Unit>(); // 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::<Unit>();
let buf_size = size_of::<Unit>(); // 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::<Unnamed>(); // value
let buf_size = size_of::<Unnamed>();
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::<Unnamed>();
let buf_size = size_of::<Unnamed>(); // 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::<u8>() // header
+ size_of::<Unnamed>(); // 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::<Unnamed>();
let buf_size = size_of::<Unnamed>(); // 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::<Named>(); // value
let buf_size = size_of::<Named>();
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::<Named>();
let buf_size = size_of::<Named>(); // 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::<u8>() // header
+ size_of::<u64>() // tag
+ size_of::<u8>() // header
+ size_of::<Named>(); // 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::<Named>();
let buf_size = size_of::<Named>(); // 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::<u32>() // discriminant
+ size_of::<Unit>(); // value
size_of::<u32>() // discriminant
+ size_of::<Unit>();
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::<u8>() // discriminant
+ size_of::<Unit>(); // value
size_of::<u8>() // discriminant
+ size_of::<Unit>();
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::<u8>() // header
+ size_of::<u64>() // tag (discriminant)
+ size_of::<u8>() // header
+ size_of::<Unit>(); // 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::<u32>() // discriminant
+ size_of::<Unit>(); // value
size_of::<u32>() // discriminant
+ size_of::<Unit>();
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();

View file

@ -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

View file

@ -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 <https://www.gnu.org/licenses/>.
use crate::Discriminant;
use std::borrow::Borrow;
use syn::Variant;
use proc_macro2::Span;
use syn::{Expr, Lit, LitInt, Variant};
pub struct DiscriminantIter<I: IntoIterator<Item: Borrow<Variant>>> {
pub struct Discriminants<I: IntoIterator<Item: Borrow<Variant>>> {
variants: I::IntoIter,
prev: Option<Discriminant>,
prev: Option<u128>,
}
impl<I: IntoIterator<Item: Borrow<Variant>>> DiscriminantIter<I> {
impl<I: IntoIterator<Item: Borrow<Variant>>> Discriminants<I> {
#[inline(always)]
#[must_use]
pub fn new(variants: I) -> Self {
@ -40,28 +39,43 @@ impl<I: IntoIterator<Item: Borrow<Variant>>> DiscriminantIter<I> {
}
}
impl<I: IntoIterator<Item: Borrow<Variant>>> Iterator for DiscriminantIter<I> {
type Item = (Discriminant, I::Item);
impl<I: IntoIterator<Item: Borrow<Variant>>> Iterator for Discriminants<I> {
type Item = LitInt;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
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::<i128>().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)]

View file

@ -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 <https://www.gnu.org/licenses/>.
use proc_macro2::TokenStream;

View file

@ -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 <https://www.gnu.org/licenses/>.
use crate::{GenericName, Repr};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{
Data,
DataEnum,
DataStruct,
DeriveInput,
Path,
Token,
};
pub fn impl_derive_macro<S, E>(
input: DeriveInput,
trait_path: Path,
r#unsafe_token: Option<Token![unsafe]>,
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
}

View file

@ -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 <https://www.gnu.org/licenses/>.
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<Self, Self::Error> {
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)
}
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
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<Self, Self::Error> {
let this = #value;
::core::result::Result::Ok(this)
}
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
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<LitInt> = Discriminants::new(&data.variants).collect();
let captures: Vec<Vec<Ident>> = 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(())
}
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
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(())
}
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
use crate::use_mod;

View file

@ -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 <https://www.gnu.org/licenses/>.
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<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
};
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
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)*;
}
}

97
librum-macros/src/lib.rs Normal file
View file

@ -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 <https://www.gnu.org/licenses/>.
#![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()
}

View file

@ -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 <https://www.gnu.org/licenses/>.
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<Self> {
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);
}
}

View file

Before

Width:  |  Height:  |  Size: 432 B

After

Width:  |  Height:  |  Size: 432 B

Before After
Before After

View file

@ -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

View file

@ -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 <https://www.gnu.org/licenses/>.
#[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<T> Buf<T> {
impl<T: Encode> Buf<T> {
/// Encodes an object into the buffer.
///
/// The object is encoded as by being passed to <code><T as [Encode]>::[encode](Encode::encode)</code>.
/// The object is encoded as by being passed to <code>&lt;T as [Encode]&gt;::[encode](Encode::encode)</code>.
///
/// # Errors
///
/// Any error that occurs during encoding is passed on and returned from this method.
#[inline]
pub fn write<U: Borrow<T>>(&mut self, value: U) -> Result<(), EncodeError> {
pub fn write<U: Borrow<T>>(&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<T: Encode> Buf<T> {
impl<T: Decode> Buf<T> {
/// Decodes an object from the buffer.
///
/// This is done as by passing the contained bytes to <code><T as [Decode]>::[decode](Decode::decode)</code>.
/// This is done as by passing the contained bytes to <code>&lt;T as [Decode]&gt;::[decode](Decode::decode)</code>.
///
/// 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<T: Decode> Buf<T> {
///
/// Any error that occurs during decoding is passed on and returned from this method.
#[inline]
pub fn read(&self) -> Result<T, DecodeError> {
pub fn read(&self) -> Result<T, T::Error> {
// We should only pass the used part of the buffer
// to `deserialise`.

View file

@ -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 <https://www.gnu.org/licenses/>.
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}'));

View file

@ -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 <https://www.gnu.org/licenses/>.
#[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<Self, DecodeError>;
fn decode(stream: &mut IStream) -> Result<Self, Self::Error>;
}
/// Implemented for tuples with up to twelve members.
#[cfg_attr(doc, doc(fake_variadic))]
impl<T> Decode for (T, )
where
T: Decode, {
impl<T: Decode> Decode for (T, ) {
type Error = T::Error;
#[inline(always)]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
let value = (Decode::decode(stream)?, );
Ok(value)
}
}
impl<T: Decode, const N: usize> Decode for [T; N] {
type Error = CollectionDecodeError<Infallible, ItemDecodeError<usize, T::Error>>;
#[inline]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
// Initialise the array incrementally.
// SAFETY: Always safe.
let mut buf: [MaybeUninit<T>; 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<T>` is
// transparent to `T`, and we have initialised
// SAFETY: This should be safe as `MaybeUninit<T>`
// 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<T: Decode, const N: usize> 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<T: Decode> Decode for Arc<T> {
type Error = T::Error;
#[inline(always)]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
Ok(Self::new(Decode::decode(stream)?))
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
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<Self, DecodeError> {
let value = u8::decode(stream)?;
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
let value = u8::decode(stream).unwrap();
match value {
0x0 => Ok(false),
0x1 => Ok(true),
_ => Err(DecodeError::InvalidBoolean(value))
_ => Err(BoolDecodeError { value })
}
}
}
impl<T: Decode> Decode for Bound<T> {
type Error = EnumDecodeError<u8, T::Error>;
#[inline(always)]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
let discriminant = u8::decode(stream)?;
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
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<T: Decode> Decode for Bound<T> {
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<T: Decode> Decode for Box<T> {
type Error = T::Error;
#[inline(always)]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
let value = Decode::decode(stream)?;
let this = Self::new(value);
@ -194,8 +227,10 @@ impl<T: Decode> Decode for Box<T> {
}
impl<T: Decode> Decode for Cell<T> {
type Error = T::Error;
#[inline(always)]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
let value = Decode::decode(stream)?;
let this = Self::new(value);
@ -204,13 +239,15 @@ impl<T: Decode> Decode for Cell<T> {
}
impl Decode for char {
#[inline]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
let value = u32::decode(stream)?;
type Error = CharDecodeError;
let this = value
#[inline]
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
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<T, B> Decode for Cow<'_, B>
where
T: DecodeBorrowed<B>,
B: ToOwned<Owned = T>,
{
type Error = T::Error;
#[inline(always)]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
let len = Decode::decode(stream)?;
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
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<Self, Self::Error> {
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<Self, DecodeError> {
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
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<K, V, S> Decode for HashMap<K, V, S>
impl<K, V, S, E> Decode for HashMap<K, V, S>
where
K: Decode + Eq + Hash,
V: Decode,
K: Decode<Error = E> + Eq + Hash,
V: Decode<Error = E>,
S: BuildHasher + Default,
{
{
type Error = CollectionDecodeError<Infallible, ItemDecodeError<usize, E>>;
#[inline]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
let len = Decode::decode(stream)?;
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
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<T, S> Decode for HashSet<T, S>
impl<K, S> Decode for HashSet<K, S>
where
T: Decode + Eq + Hash,
K: Decode + Eq + Hash,
S: BuildHasher + Default,
{
{
type Error = CollectionDecodeError<Infallible, ItemDecodeError<usize, K::Error>>;
#[inline]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
let len = Decode::decode(stream)?;
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
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<Self, DecodeError> {
fn decode(_stream: &mut IStream) -> Result<Self, Self::Error> {
panic!("cannot deserialise `Infallible` as it cannot be serialised to begin with")
}
}
impl Decode for IpAddr {
type Error = EnumDecodeError<u8, Infallible>;
#[inline]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
let discriminant = u8::decode(stream)?;
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
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<Self, DecodeError> {
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
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<Self, DecodeError> {
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
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<Self, DecodeError> {
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
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<T: Decode> Decode for LinkedList<T> {
type Error = CollectionDecodeError<Infallible, ItemDecodeError<usize, T::Error>>;
#[inline]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
let len = usize::decode(stream)?;
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
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<T: Decode> Decode for LinkedList<T> {
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
impl<T: Decode> Decode for Mutex<T> {
type Error = T::Error;
#[inline(always)]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
Ok(Self::new(Decode::decode(stream)?))
}
}
impl<T: Decode> Decode for Option<T> {
type Error = T::Error;
#[expect(clippy::if_then_some_else_none)] // ???
#[inline]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
let sign = bool::decode(stream)?;
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
let sign = bool::decode(stream).unwrap();
let this = if sign {
Some(Decode::decode(stream)?)
@ -397,22 +481,28 @@ impl<T: Decode> Decode for Option<T> {
}
impl<T> Decode for PhantomData<T> {
type Error = Infallible;
#[inline(always)]
fn decode(_stream: &mut IStream) -> Result<Self, DecodeError> {
fn decode(_stream: &mut IStream) -> Result<Self, Self::Error> {
Ok(Self)
}
}
impl Decode for PhantomPinned {
type Error = Infallible;
#[inline(always)]
fn decode(_stream: &mut IStream) -> Result<Self, DecodeError> {
fn decode(_stream: &mut IStream) -> Result<Self, Self::Error> {
Ok(Self)
}
}
impl<T: Decode> Decode for Range<T> {
type Error = T::Error;
#[inline(always)]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
let start = Decode::decode(stream)?;
let end = Decode::decode(stream)?;
@ -421,8 +511,10 @@ impl<T: Decode> Decode for Range<T> {
}
impl<T: Decode> Decode for RangeFrom<T> {
type Error = T::Error;
#[inline(always)]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
let start = Decode::decode(stream)?;
Ok(start..)
@ -430,15 +522,19 @@ impl<T: Decode> Decode for RangeFrom<T> {
}
impl Decode for RangeFull {
type Error = Infallible;
#[inline(always)]
fn decode(_stream: &mut IStream) -> Result<Self, DecodeError> {
fn decode(_stream: &mut IStream) -> Result<Self, Self::Error> {
Ok(..)
}
}
impl<T: Decode> Decode for RangeInclusive<T> {
type Error = T::Error;
#[inline(always)]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
let start = Decode::decode(stream)?;
let end = Decode::decode(stream)?;
@ -447,8 +543,10 @@ impl<T: Decode> Decode for RangeInclusive<T> {
}
impl<T: Decode> Decode for RangeTo<T> {
type Error = T::Error;
#[inline(always)]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
let end = Decode::decode(stream)?;
Ok(..end)
@ -456,8 +554,10 @@ impl<T: Decode> Decode for RangeTo<T> {
}
impl<T: Decode> Decode for RangeToInclusive<T> {
type Error = T::Error;
#[inline(always)]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
let end = Decode::decode(stream)?;
Ok(..=end)
@ -467,15 +567,19 @@ impl<T: Decode> Decode for RangeToInclusive<T> {
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<T: Decode> Decode for Rc<T> {
type Error = T::Error;
#[inline(always)]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
Ok(Self::new(Decode::decode(stream)?))
}
}
impl<T: Decode> Decode for RefCell<T> {
type Error = T::Error;
#[inline(always)]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
let value = Decode::decode(stream)?;
let this = Self::new(value);
@ -483,15 +587,28 @@ impl<T: Decode> Decode for RefCell<T> {
}
}
impl<T: Decode, E: Decode> Decode for core::result::Result<T, E> {
impl<T, E, Err> Decode for core::result::Result<T, E>
where
T: Decode<Error = Err>,
E: Decode<Error = Err>,
{
type Error = EnumDecodeError<bool, Err>;
#[inline]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
let sign = bool::decode(stream)?;
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
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<T: Decode, E: Decode> Decode for core::result::Result<T, E> {
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
impl<T: Decode> Decode for RwLock<T> {
type Error = T::Error;
#[inline(always)]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
Ok(Self::new(Decode::decode(stream)?))
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
let value = Decode::decode(stream)?;
let this = Self::new(value);
Ok(this)
}
}
impl<T: Decode> Decode for Saturating<T> {
type Error = T::Error;
#[inline(always)]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
Ok(Self(Decode::decode(stream)?))
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
let value = Decode::decode(stream)?;
let this = Self(value);
Ok(this)
}
}
impl Decode for SocketAddr {
type Error = EnumDecodeError<u8, Infallible>;
#[inline]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
let discriminant = u8::decode(stream)?;
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
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<Self, DecodeError> {
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
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<Self, DecodeError> {
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
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<Infallible, Utf8Error>;
#[inline(always)]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
let len = Decode::decode(stream)?;
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
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<Self, DecodeError> {
let time = i64::decode(stream)?;
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
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<Self, DecodeError> {
fn decode(_stream: &mut IStream) -> Result<Self, Self::Error> {
Ok(())
}
}
impl Decode for usize {
type Error = Infallible;
#[inline(always)]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
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<T: Decode> Decode for Vec<T> {
type Error = CollectionDecodeError<Infallible, ItemDecodeError<usize, T::Error>>;
#[inline]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
let len = Decode::decode(stream)?;
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
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<T: Decode> Decode for Vec<T> {
}
impl<T: Decode> Decode for Wrapping<T> {
type Error = T::Error;
#[inline(always)]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
Ok(Self(Decode::decode(stream)?))
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
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<Self, ::bzipper::error::DecodeError> {
fn decode(stream: &mut IStream) -> ::core::result::Result<Self, Self::Error> {
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<Error = E>, )* {
type Error = E;
#[inline(always)]
fn decode(stream: &mut ::bzipper::IStream) -> ::core::result::Result<Self, ::bzipper::error::DecodeError> {
fn decode(stream: &mut ::librum::IStream) -> ::core::result::Result<Self, Self::Error> {
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<Self, ::bzipper::error::DecodeError> {
let value = <$ty as ::bzipper::Decode>::decode(stream)?;
fn decode(stream: &mut IStream) -> ::core::result::Result<Self, Self::Error> {
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<Self, ::bzipper::error::DecodeError> {
Ok(Self::new(::bzipper::Decode::decode(stream)?))
fn decode(stream: &mut ::librum::IStream) -> ::core::result::Result<Self, Self::Error> {
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);

View file

@ -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 <https://www.gnu.org/licenses/>.
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<u16>: [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<u16>: [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}");
}

View file

@ -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 <https://www.gnu.org/licenses/>.
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 <code>&lt;Self as [Decode]&gt;::[decode](Decode::decode)</code> 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 <code>DecodeBorrowed&lt;[\[T\]]&gt;</code> 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<B: ?Sized>: Borrow<B> + Decode { }
impl<T: Decode> DecodeBorrowed<T> for T { }
#[cfg(all(feature = "alloc", target_has_atomic = "ptr"))]
#[cfg_attr(doc, doc(cfg(all(feature = "alloc", target_has_atomic = "ptr"))))]
impl<T: Decode> DecodeBorrowed<T> for Arc<T> { }
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<T: Decode> DecodeBorrowed<T> for Box<T> { }
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl DecodeBorrowed<core::ffi::CStr> for CString { }
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<T: Decode> DecodeBorrowed<T> for Rc<T> { }
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl DecodeBorrowed<str> for String { }
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<T: Decode> DecodeBorrowed<[T]> for Vec<T> { }

View file

@ -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 <https://www.gnu.org/licenses/>.
#[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<T: Encode> Encode for &T {
impl<T: Encode + ?Sized> 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<T: Encode> Encode for &mut T {
impl<T: Encode + ?Sized> 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<T: Encode> Encode for &mut T {
/// Implemented for tuples with up to twelve members.
#[cfg_attr(doc, doc(fake_variadic))]
impl<T: Encode> 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<T: Encode, const N: usize> Encode for [T; N] {
type Error = CollectionEncodeError<Infallible, ItemEncodeError<usize, T::Error>>;
/// 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<T: Encode, const N: usize> Encode for [T; N] {
}
impl<T: Encode> Encode for [T] {
type Error = CollectionEncodeError<UsizeEncodeError, ItemEncodeError<usize, T::Error>>;
/// 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<T: Encode> Encode for Arc<T> {
#[cfg(all(feature = "alloc", target_has_atomic = "ptr"))]
#[cfg_attr(doc, doc(cfg(all(feature = "alloc", target_has_atomic = "ptr"))))]
impl<T: Encode + ?Sized> Encode for Arc<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 bool {
type Error = <u8 as Encode>::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<T: Encode> Encode for Bound<T> {
type Error = EnumEncodeError<u8, T::Error>;
#[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<T: Encode> Encode for Bound<T> {
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<T: Encode> Encode for Box<T> {
impl<T: Encode + ?Sized> Encode for Box<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<T: Encode + Copy> Encode for Cell<T> {
impl<T: Copy + Encode> Encode for Cell<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.get().encode(stream)
}
}
impl Encode for char {
type Error = <u32 as Encode>::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<T: Encode + ToOwned> Encode for Cow<'_, T> {
impl<T: Encode + ?Sized + ToOwned> 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 = <CStr as Encode>::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<K, V, S> Encode for HashMap<K, V, S>
impl<K, V, S, E> Encode for HashMap<K, V, S>
where
K: Encode,
V: Encode,
K: Encode<Error = E>,
V: Encode<Error = E>,
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<T, S> Encode for HashSet<T, S>
impl<K, S> Encode for HashSet<K, S>
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<T, Infallible>`.
// **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<u8, Infallible>;
/// 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<T: Encode> Encode for LazyCell<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)
}
}
@ -394,19 +459,30 @@ impl<T: Encode> Encode for LazyCell<T> {
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
impl<T: Encode> Encode for LazyLock<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)
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<T: Encode> Encode for LinkedList<T> {
impl<T: Encode<Error = E>, E> Encode for LinkedList<T> {
type Error = CollectionEncodeError<UsizeEncodeError, (usize, E)>;
#[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<T: Encode> Encode for LinkedList<T> {
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
impl<T: Encode> Encode for Mutex<T> {
impl<T: Encode + ?Sized> Encode for Mutex<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
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
@ -426,16 +504,20 @@ impl<T: Encode> Encode for Mutex<T> {
}
impl<T: Encode> Encode for Option<T> {
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<T: Encode> Encode for Option<T> {
}
impl<T> Encode for PhantomData<T> {
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<T: Encode> Encode for Range<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.start.encode(stream)?;
self.end.encode(stream)?;
@ -469,22 +557,28 @@ impl<T: Encode> Encode for Range<T> {
}
impl<T: Encode> Encode for RangeFrom<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.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<T: Encode> Encode for RangeInclusive<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.start().encode(stream)?;
self.end().encode(stream)?;
@ -493,15 +587,19 @@ impl<T: Encode> Encode for RangeInclusive<T> {
}
impl<T: Encode> Encode for RangeTo<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.end.encode(stream)
}
}
impl<T: Encode> Encode for RangeToInclusive<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.end.encode(stream)?;
Ok(())
@ -510,42 +608,54 @@ impl<T: Encode> Encode for RangeToInclusive<T> {
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<T: Encode> Encode for Rc<T> {
impl<T: Encode + ?Sized> Encode for Rc<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<T: Encode> Encode for RefCell<T> {
impl<T: Encode + ?Sized> Encode for RefCell<T> {
type Error = RefCellEncodeError<T::Error>;
#[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<T: Encode, E: Encode> Encode for core::result::Result<T, E> {
impl<T, E, Err> Encode for core::result::Result<T, E>
where
T: Encode<Error = Err>,
E: Encode<Error = Err>,
{
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<T: Encode, E: Encode> Encode for core::result::Result<T, E> {
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
impl<T: Encode> Encode for RwLock<T> {
impl<T: Encode + ?Sized> Encode for RwLock<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
.read()
.or_else(|e| Ok(e.into_inner()))?
@ -567,17 +679,21 @@ impl<T: Encode> Encode for RwLock<T> {
}
impl<T: Encode> Encode for Saturating<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 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 = <str as Encode>::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<T: Encode> Encode for Vec<T> {
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<T: Encode> Encode for Wrapping<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)
}
}
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<Error = E>, )* {
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)
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
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<u16>: 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,
]);
}

View file

@ -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 <https://www.gnu.org/licenses/>.
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 <code>&lt;bool as [Decode](crate::Decode)&gt;::[decode](crate::Decode::decode)</code>, 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 { }

View file

@ -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 <https://www.gnu.org/licenses/>.
use core::error::Error;
use core::fmt::{self, Display, Formatter};
/// A C-like string could not be decoded.
///
/// This error is generatead when <code>&lt;[CString](alloc::ffi::CString) as [Decode](crate::Decode)&gt;::[decode](crate::Decode::decode)</code> 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 { }

View file

@ -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 <https://www.gnu.org/licenses/>.
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 <code>&lt;char as [Decode](crate::Decode)&gt;::[decode](crate::Decode::decode)</code>, 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 { }

View file

@ -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 <https://www.gnu.org/licenses/>.
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 <code>CollectionDecodeError<[Infallible](core::convert::Infallible), [ItemDecodeError](crate::error::ItemDecodeError)<[usize], ..></code>, 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 <code>CollectionDecodeError<[`SizeError`](crate::error::SizeError), [Utf8Error](crate::error::Utf8Error)></code>.
#[derive(Debug)]
#[must_use]
pub enum CollectionDecodeError<L, I> {
/// 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<L, I> Display for CollectionDecodeError<L, I>
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<L, I> Error for CollectionDecodeError<L, I>
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<L, I> From<CollectionDecodeError<L, I>> for Infallible
where
L: Into<Self>,
I: Into<Self>,
{
#[inline(always)]
fn from(_value: CollectionDecodeError<L, I>) -> Self {
unreachable!()
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
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<L, I> {
/// The collection length could not be encoded.
Length(L),
/// A collection item could not be encoded.
Item(I),
}
impl<L, I> Display for CollectionEncodeError<L, I>
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<L, I> Error for CollectionEncodeError<L, I>
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<L, I> From<CollectionEncodeError<L, I>> for Infallible
where
L: Into<Self>,
I: Into<Self>,
{
#[inline(always)]
fn from(_value: CollectionEncodeError<L, I>) -> Self {
unreachable!()
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
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<D: Decode, F> {
/// 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<D, F> Display for EnumDecodeError<D, F>
where
D: Decode<Error: Display> + 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<D, F> Error for EnumDecodeError<D, F>
where
D: Debug + Decode<Error: Error + 'static> + 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<D, F> From<EnumDecodeError<D, F>> for Infallible
where
D: Decode<Error: Into<Self>>,
F: Into<Self>,
{
#[inline(always)]
fn from(_value: EnumDecodeError<D, F>) -> Self {
unreachable!()
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
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<D: Encode, F> {
/// The discriminant could not be encoded.
Discriminant(D::Error),
/// A field could not be encoded.
Field(F),
}
impl<D, F> Display for EnumEncodeError<D, F>
where
D: Display + Encode<Error: Display>,
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<D, F> Error for EnumEncodeError<D, F>
where
D: Debug + Display + Encode<Error: Error + 'static>,
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<D, F> From<EnumEncodeError<D, F>> for Infallible
where
D: Encode<Error: Into<Self>>,
F: Into<Self>,
{
#[inline(always)]
fn from(_value: EnumEncodeError<D, F>) -> Self {
unreachable!()
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
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<BoolDecodeError> for GenericDecodeError {
#[inline(always)]
fn from(value: BoolDecodeError) -> Self {
Self::InvalidBool(value)
}
}
impl<L, I> From<CollectionDecodeError<L, I>> for GenericDecodeError
where
L: Into<Self>,
I: Into<Self>,
{
#[inline(always)]
fn from(value: CollectionDecodeError<L, I>) -> Self {
use CollectionDecodeError::*;
match value {
Length(e) => e.into(),
Item(e) => e.into(),
}
}
}
impl<D, F> From<EnumDecodeError<D, F>> for GenericDecodeError
where
D: Decode<Error: Into<Self>> + PrimitiveDiscriminant,
F: Into<Self>,
{
#[inline(always)]
fn from(value: EnumDecodeError<D, F>) -> Self {
use EnumDecodeError::*;
match value {
InvalidDiscriminant(e) => e.into(),
UnassignedDiscriminant { value } => Self::UnassignedDiscriminant { value: value.to_u128() },
Field(e) => e.into(),
}
}
}
impl From<Infallible> for GenericDecodeError {
#[inline(always)]
fn from(_value: Infallible) -> Self {
unsafe { unreachable_unchecked() }
}
}
impl<I, E: Into<Self>> From<ItemDecodeError<I, E>> for GenericDecodeError {
#[inline(always)]
fn from(value: ItemDecodeError<I, E>) -> Self {
value.error.into()
}
}
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
impl From<CStringDecodeError> for GenericDecodeError {
#[inline(always)]
fn from(value: CStringDecodeError) -> Self {
Self::NullString(value)
}
}
impl From<NonZeroDecodeError> for GenericDecodeError {
#[inline(always)]
fn from(value: NonZeroDecodeError) -> Self {
Self::NullInteger(value)
}
}
impl From<SizeError> for GenericDecodeError {
#[inline(always)]
fn from(value: SizeError) -> Self {
Self::SmallBuffer(value)
}
}
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
impl From<SystemTimeDecodeError> for GenericDecodeError {
#[inline(always)]
fn from(value: SystemTimeDecodeError) -> Self {
Self::NarrowSystemTime(value)
}
}
impl From<Utf8Error> for GenericDecodeError {
#[inline(always)]
fn from(value: Utf8Error) -> Self {
Self::BadString(value)
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
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<BorrowError> for GenericEncodeError {
#[inline(always)]
fn from(value: BorrowError) -> Self {
Self::BadBorrow(value)
}
}
impl<L, I> From<CollectionEncodeError<L, I>> for GenericEncodeError
where
L: Into<Self>,
I: Into<Self>,
{
#[inline(always)]
fn from(value: CollectionEncodeError<L, I>) -> Self {
use CollectionEncodeError::*;
match value {
Length(e) => e.into(),
Item(e) => e.into(),
}
}
}
impl<D, F> From<EnumEncodeError<D, F>> for GenericEncodeError
where
D: Encode<Error: Into<Self>>,
F: Into<Self>,
{
#[inline(always)]
fn from(value: EnumEncodeError<D, F>) -> Self {
use EnumEncodeError::*;
match value {
Discriminant(e) => e.into(),
Field(e) => e.into(),
}
}
}
impl From<Infallible> for GenericEncodeError {
#[inline(always)]
fn from(_value: Infallible) -> Self {
unsafe { unreachable_unchecked() }
}
}
impl From<IsizeEncodeError> for GenericEncodeError {
#[inline(always)]
fn from(value: IsizeEncodeError) -> Self {
Self::LargeIsize(value)
}
}
impl<I, E: Into<Self>> From<ItemEncodeError<I, E>> for GenericEncodeError {
#[inline(always)]
fn from(value: ItemEncodeError<I, E>) -> Self {
value.error.into()
}
}
impl From<UsizeEncodeError> for GenericEncodeError {
#[inline(always)]
fn from(value: UsizeEncodeError) -> Self {
Self::LargeUsize(value)
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
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 { }

View file

@ -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 <https://www.gnu.org/licenses/>.
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<I, E> {
/// The index of the invalid item.
pub index: I,
/// The decoder's error.
pub error: E,
}
impl<I, E> Display for ItemDecodeError<I, E>
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<I, E> Error for ItemDecodeError<I, E>
where
Self: Debug + Display,
E: Error + 'static,
{
#[inline(always)]
fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(&self.error)
}
}
impl<I, E> From<ItemDecodeError<I, E>> for Infallible {
#[inline(always)]
fn from(_value: ItemDecodeError<I, E>) -> Self {
unreachable!()
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
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<I, E> {
/// The index of the invalid item.
pub index: I,
/// The encoder's error.
pub error: E,
}
impl<I, E> Display for ItemEncodeError<I, E>
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<I, E> Error for ItemEncodeError<I, E>
where
Self: Debug + Display,
E: Error + 'static,
{
#[inline(always)]
fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(&self.error)
}
}
impl<I, E: Into<Self>> From<ItemEncodeError<I, E>> for Infallible {
#[inline(always)]
fn from(_value: ItemEncodeError<I, E>) -> Self {
unreachable!()
}
}

52
librum/src/error/mod.rs Normal file
View file

@ -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 <https://www.gnu.org/licenses/>.
//! 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);

View file

@ -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 <https://www.gnu.org/licenses/>.
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 <code>[NonZero](core::num::NonZero)&lt;T&gt;</code> 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 { }

View file

@ -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 <https://www.gnu.org/licenses/>.
use core::cell::BorrowError;
use core::error::Error;
use core::fmt::{self, Display, Formatter};
/// A reference cell could not be encoded.
///
/// The implementation of <code>&lt;[RefCell](core::cell::RefCell)&lt;T&gt; as [Encode](crate::Encode)&gt;::[encode](crate::Encode::encode)</code> will first attempt to call <code>RefCell::[borrow](core::cell::RefCell::borrow)</code>.
/// If this call fails, then the returned error is again returned as a [`Borrow`](Self::Borrow) instance.
/// If the following call to <code>T::encode</code> 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<E> {
/// The reference cell could not be borrowed.
Borrow(BorrowError),
/// The contained value could not be encoded.
Value(E),
}
impl<E: Display> Display for RefCellEncodeError<E> {
#[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<E: Error + 'static> Error for RefCellEncodeError<E> {
#[inline(always)]
fn source(&self) -> Option<&(dyn Error + 'static)> {
use RefCellEncodeError::*;
match *self {
Borrow(ref e) => Some(e),
Value(ref e) => Some(e)
}
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
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)
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
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),
}
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
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 { }

View file

@ -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 <https://www.gnu.org/licenses/>.
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 { }

View file

@ -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 <https://www.gnu.org/licenses/>.
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,

View file

@ -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 <https://www.gnu.org/licenses/>.
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,

View file

@ -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 <https://www.gnu.org/licenses/>.
use core::slice;

View file

@ -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 <https://www.gnu.org/licenses/>.
#![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** &#8594; | 7.009 | 3.528 | 3.071 | 17.289 | 3.599 |
//! | **Total deviation (p.c.)** &#8594; | +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** &#8594; | 9.553 | 8.010 | 6.001 | 16.691 |
//! | **Total deviation (p.c.)** &#8594; | +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 <code>Encode&lt;[Error]: [Into]&lt;[GenericEncodeError]&gt;&gt;</code>.
/// 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::<i16>());
///
/// 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:
///
/// <code>[EnumEncodeError]&lt;&lt;Repr as Encode&gt;::Error, GenericEncodeError&gt;</code>,
///
/// [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 <code>T*n*::MAX_ENCODED_SIZE</code> wherein <code>T*n*</code> 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);

View file

@ -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 <https://www.gnu.org/licenses/>.
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;

View file

@ -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 <https://www.gnu.org/licenses/>.
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);

View file

@ -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 <https://www.gnu.org/licenses/>.
#[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<T: SizedEncode> SizedEncode for &T {
impl<T: SizedEncode> SizedEncode for &T {
const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE;
}
unsafe impl<T: SizedEncode> SizedEncode for &mut T {
impl<T: SizedEncode> 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<T: SizedEncode> SizedEncode for (T, ) {
impl<T: SizedEncode> SizedEncode for (T, ) {
#[doc(hidden)]
const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE;
}
unsafe impl<T: SizedEncode, const N: usize> SizedEncode for [T; N] {
impl<T: SizedEncode, const N: usize> 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<T: SizedEncode> SizedEncode for Arc<T> {
#[cfg(all(feature = "alloc", target_has_atomic = "ptr"))]
#[cfg_attr(doc, doc(cfg(all(feature = "alloc", target_has_atomic = "ptr"))))]
impl<T: SizedEncode> SizedEncode for Arc<T> {
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<T: SizedEncode> SizedEncode for Bound<T> {
impl<T: SizedEncode> SizedEncode for Bound<T> {
const MAX_ENCODED_SIZE: usize = 0x0;
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
unsafe impl<T: SizedEncode> SizedEncode for Box<T> {
impl<T: SizedEncode> SizedEncode for Box<T> {
const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE;
}
unsafe impl<T: Copy + SizedEncode> SizedEncode for Cell<T> {
impl<T: Copy + SizedEncode> SizedEncode for Cell<T> {
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<T: SizedEncode + ToOwned> SizedEncode for Cow<'_, T> {
impl<T: SizedEncode + ToOwned> 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<T: SizedEncode> SizedEncode for LazyCell<T> {
impl<T: SizedEncode> SizedEncode for LazyCell<T> {
const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE;
}
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
unsafe impl<T: SizedEncode> SizedEncode for LazyLock<T> {
impl<T: SizedEncode> SizedEncode for LazyLock<T> {
const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE;
}
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
unsafe impl<T: SizedEncode> SizedEncode for Mutex<T> {
impl<T: SizedEncode> SizedEncode for Mutex<T> {
const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE;
}
unsafe impl<T: SizedEncode> SizedEncode for Option<T> {
impl<T: SizedEncode> SizedEncode for Option<T> {
const MAX_ENCODED_SIZE: usize =
bool::MAX_ENCODED_SIZE
+ T::MAX_ENCODED_SIZE;
}
unsafe impl<T> SizedEncode for PhantomData<T> {
impl<T> SizedEncode for PhantomData<T> {
const MAX_ENCODED_SIZE: usize = 0x0;
}
unsafe impl<T: SizedEncode> SizedEncode for Range<T> {
impl<T: SizedEncode> SizedEncode for Range<T> {
const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE * 0x2;
}
unsafe impl<T: SizedEncode> SizedEncode for RangeFrom<T> {
impl<T: SizedEncode> SizedEncode for RangeFrom<T> {
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<T: SizedEncode> SizedEncode for RangeInclusive<T> {
impl<T: SizedEncode> SizedEncode for RangeInclusive<T> {
const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE * 0x2;
}
unsafe impl<T: SizedEncode> SizedEncode for RangeTo<T> {
impl<T: SizedEncode> SizedEncode for RangeTo<T> {
const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE;
}
unsafe impl<T: SizedEncode> SizedEncode for RangeToInclusive<T> {
impl<T: SizedEncode> SizedEncode for RangeToInclusive<T> {
const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE;
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
unsafe impl<T: SizedEncode> SizedEncode for Rc<T> {
impl<T: SizedEncode> SizedEncode for Rc<T> {
const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE;
}
unsafe impl<T: SizedEncode> SizedEncode for RefCell<T> {
impl<T: SizedEncode> SizedEncode for RefCell<T> {
const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE;
}
unsafe impl<T: SizedEncode, E: SizedEncode> SizedEncode for core::result::Result<T, E> {
impl<T, E, Err> SizedEncode for core::result::Result<T, E>
where
T: SizedEncode + Encode<Error = Err>,
E: SizedEncode + Encode<Error = Err>,
{
const MAX_ENCODED_SIZE: usize =
bool::MAX_ENCODED_SIZE
+ if size_of::<T>() > size_of::<E>() { size_of::<T>() } else { size_of::<E>() };
@ -228,24 +230,24 @@ unsafe impl<T: SizedEncode, E: SizedEncode> SizedEncode for core::result::Result
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
unsafe impl<T: SizedEncode> SizedEncode for RwLock<T> {
impl<T: SizedEncode> SizedEncode for RwLock<T> {
const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE;
}
unsafe impl<T: SizedEncode> SizedEncode for Saturating<T> {
impl<T: SizedEncode> SizedEncode for Saturating<T> {
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<T: SizedEncode> SizedEncode for Wrapping<T> {
impl<T: SizedEncode> SizedEncode for Wrapping<T> {
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<Error = E>, )* {
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;
}
};
}

View file

@ -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 <https://www.gnu.org/licenses/>.
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);
}

View file

@ -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 <https://www.gnu.org/licenses/>.
#[cfg(test)]
mod test;
mod tests;
use core::iter::{DoubleEndedIterator, ExactSizeIterator, FusedIterator};
use core::mem::MaybeUninit;
@ -87,7 +87,6 @@ impl<T, const N: usize> AsRef<[T]> for SizedIter<T, N> {
}
impl<T: Clone, const N: usize> Clone for SizedIter<T, N> {
#[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<T>; N] = unsafe { MaybeUninit::uninit().assume_init() };

View file

@ -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 <https://www.gnu.org/licenses/>.
use bzipper::{SizedSlice, SizedStr};
use librum::{SizedSlice, SizedStr};
#[test]
fn test_sized_iter_clone() {

View file

@ -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 <https://www.gnu.org/licenses/>.
use crate::SizedSlice;
use core::cmp::Ordering;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
impl<T: Eq, const N: usize> Eq for SizedSlice<T, N> { }
impl<T: Ord, const N: usize> Ord for SizedSlice<T, N> {
#[inline(always)]
fn cmp(&self, other: &Self) -> Ordering {
self.as_slice().cmp(other.as_slice())
}
}
impl<T: PartialEq<U>, U: PartialEq<T>, const N: usize, const M: usize> PartialEq<SizedSlice<U, M>> for SizedSlice<T, N> {
#[inline(always)]
fn eq(&self, other: &SizedSlice<U, M>) -> bool {
self.as_slice() == other.as_slice()
}
}
impl<T: PartialEq<U>, U: PartialEq<T>, const N: usize, const M: usize> PartialEq<[U; M]> for SizedSlice<T, N> {
#[inline(always)]
fn eq(&self, other: &[U; M]) -> bool {
self.as_slice() == other.as_slice()
}
}
impl<T: PartialEq<U>, U: PartialEq<T>, const N: usize> PartialEq<&[U]> for SizedSlice<T, N> {
#[inline(always)]
fn eq(&self, other: &&[U]) -> bool {
self.as_slice() == *other
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<T: PartialEq<U>, U: PartialEq<T>, const N: usize> PartialEq<Vec<U>> for SizedSlice<T, N> {
#[inline(always)]
fn eq(&self, other: &Vec<U>) -> bool {
self.as_slice() == other.as_slice()
}
}
impl<T: PartialOrd, const N: usize, const M: usize> PartialOrd<SizedSlice<T, M>> for SizedSlice<T, N> {
#[inline(always)]
fn partial_cmp(&self, other: &SizedSlice<T, M>) -> Option<Ordering> {
self.as_slice().partial_cmp(other.as_slice())
}
}
impl<T: PartialOrd, const N: usize, const M: usize> PartialOrd<[T; M]> for SizedSlice<T, N> {
#[inline(always)]
fn partial_cmp(&self, other: &[T; M]) -> Option<Ordering> {
self.as_slice().partial_cmp(other.as_slice())
}
}
impl<T: PartialOrd, const N: usize> PartialOrd<&[T]> for SizedSlice<T, N> {
#[inline(always)]
fn partial_cmp(&self, other: &&[T]) -> Option<Ordering> {
self.as_slice().partial_cmp(*other)
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<T: PartialOrd, const N: usize> PartialOrd<Vec<T>> for SizedSlice<T, N> {
#[inline(always)]
fn partial_cmp(&self, other: &Vec<T>) -> Option<Ordering> {
self.as_slice().partial_cmp(other.as_slice())
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
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<T, const N: usize> SizedSlice<T, N> {
/// 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<T>; 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<T>; 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::<T>(len).unwrap();
let ptr = alloc(layout).cast::<T>();
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<T> {
self.into_boxed_slice().into_vec()
}
}
impl<T, const N: usize> AsMut<[T]> for SizedSlice<T, N> {
#[inline(always)]
fn as_mut(&mut self) -> &mut [T] {
self.as_mut_slice()
}
}
impl<T, const N: usize> AsRef<[T]> for SizedSlice<T, N> {
#[inline(always)]
fn as_ref(&self) -> &[T] {
self.as_slice()
}
}
impl<T, const N: usize> Borrow<[T]> for SizedSlice<T, N> {
#[inline(always)]
fn borrow(&self) -> &[T] {
self.as_slice()
}
}
impl<T, const N: usize> BorrowMut<[T]> for SizedSlice<T, N> {
#[inline(always)]
fn borrow_mut(&mut self) -> &mut [T] {
self.as_mut_slice()
}
}
impl<T, const N: usize> Deref for SizedSlice<T, N> {
type Target = [T];
#[inline(always)]
fn deref(&self) -> &Self::Target {
self.as_slice()
}
}
impl<T, const N: usize> DerefMut for SizedSlice<T, N> {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target {
self.as_mut_slice()
}
}
impl<T, const N: usize> From<[T; N]> for SizedSlice<T, N> {
#[inline(always)]
fn from(value: [T; N]) -> Self {
unsafe {
let buf = value.as_ptr().cast::<[MaybeUninit<T>; N]>().read();
Self { buf, len: N }
}
}
}
impl<T: Clone, const N: usize> TryFrom<&[T]> for SizedSlice<T, N> {
type Error = SizeError;
#[inline(always)]
fn try_from(value: &[T]) -> Result<Self, Self::Error> {
Self::new(value)
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<T, const N: usize> From<SizedSlice<T, N>> for Box<[T]> {
#[inline(always)]
fn from(value: SizedSlice<T, N>) -> Self {
value.into_boxed_slice()
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<T, const N: usize> From<SizedSlice<T, N>> for Vec<T> {
#[inline(always)]
fn from(value: SizedSlice<T, N>) -> Self {
value.into_vec()
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
use crate::{SizedIter, SizedSlice};
use core::mem::MaybeUninit;
use core::slice;
impl<T, const N: usize> FromIterator<T> for SizedSlice<T, N> {
#[inline]
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
let mut iter = iter.into_iter();
let mut buf: [MaybeUninit<T>; 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<T, const N: usize> IntoIterator for SizedSlice<T, N> {
type Item = T;
type IntoIter = SizedIter<T, N>;
#[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<T, N> {
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<T, N> {
type Item = &'a mut T;
type IntoIter = slice::IterMut<'a, T>;
#[inline(always)]
fn into_iter(self) -> Self::IntoIter {
self.iter_mut()
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
#[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::<u8, 0x4>::try_from([0x3].as_slice()).unwrap();
/// let vec1 = SizedSlice::<u8, 0x4>::try_from([0x3, 0x2].as_slice()).unwrap();
/// let vec2 = SizedSlice::<u8, 0x4>::try_from([0x3, 0x2, 0x4].as_slice()).unwrap();
/// let vec3 = SizedSlice::<u8, 0x4>::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<T, const N: usize> {
buf: [MaybeUninit<T>; N],
len: usize,
}
impl<T, const N: usize> SizedSlice<T, N> {
/// 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::<T>(); 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::<T>(); 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<T: Clone, const N: usize> SizedSlice<T, N> {
/// Constructs an empty, fixed-size vector.
#[inline]
pub fn new(data: &[T]) -> Result<Self, SizeError> {
let mut buf: [MaybeUninit<T>; 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<T: Clone, const N: usize> Clone for SizedSlice<T, N> {
#[inline]
fn clone(&self) -> Self {
unsafe {
let mut buf: [MaybeUninit<T>; 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<T: Debug, const N: usize> Debug for SizedSlice<T, N> {
#[inline(always)]
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Debug::fmt(self.as_slice(), f)
}
}
impl<T, const N: usize> Default for SizedSlice<T, N> {
#[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<T: Hash, const N: usize> Hash for SizedSlice<T, N> {
#[inline(always)]
fn hash<H: Hasher>(&self, state: &mut H) {
for v in self {
v.hash(state);
}
}
}
impl<T, I: SliceIndex<[T]>, const N: usize> Index<I> for SizedSlice<T, N> {
type Output = I::Output;
#[inline(always)]
fn index(&self, index: I) -> &Self::Output {
self.get(index).unwrap()
}
}
impl<T, I: SliceIndex<[T]>, const N: usize> IndexMut<I> for SizedSlice<T, N> {
#[inline(always)]
fn index_mut(&mut self, index: I) -> &mut Self::Output {
self.get_mut(index).unwrap()
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
use crate::{
Decode,
DecodeBorrowed,
Encode,
IStream,
OStream,
SizedEncode,
SizedSlice
};
use crate::error::{CollectionDecodeError, ItemDecodeError, SizeError};
use core::mem::MaybeUninit;
impl<T: Decode, const N: usize> Decode for SizedSlice<T, N> {
type Error = CollectionDecodeError<SizeError, ItemDecodeError<usize, T::Error>>;
#[inline]
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
let len = Decode::decode(stream).unwrap();
if len > N { return Err(CollectionDecodeError::Length(SizeError { cap: N, len })) };
let mut buf: [MaybeUninit<T>; 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<T: Decode, const N: usize> DecodeBorrowed<[T]> for SizedSlice<T, N> { }
impl<T: Encode, const N: usize> Encode for SizedSlice<T, N> {
type Error = <[T] as Encode>::Error;
#[inline(always)]
fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> {
self.as_slice().encode(stream)
}
}
impl<T: SizedEncode, const N: usize> SizedEncode for SizedSlice<T, N> {
const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE * N;
}

View file

@ -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 <https://www.gnu.org/licenses/>.
use alloc::vec::Vec;
use bzipper::SizedSlice;
use librum::SizedSlice;
use std::vec::Vec;
#[test]
fn test_sized_slice_from_iter() {

View file

@ -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 <https://www.gnu.org/licenses/>.
use crate::SizedStr;
use core::cmp::Ordering;
#[cfg(feature = "alloc")]
use alloc::string::String;
impl<const N: usize> Ord for SizedStr<N> {
#[inline(always)]
fn cmp(&self, other: &Self) -> Ordering {
self.as_str().cmp(other.as_str())
}
}
impl<const N: usize, const M: usize> PartialEq<SizedStr<M>> for SizedStr<N> {
#[inline(always)]
fn eq(&self, other: &SizedStr<M>) -> bool {
self.as_str() == other.as_str()
}
}
impl<const N: usize> PartialEq<&str> for SizedStr<N> {
#[inline(always)]
fn eq(&self, other: &&str) -> bool {
self.as_str() == *other
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<const N: usize> PartialEq<String> for SizedStr<N> {
#[inline(always)]
fn eq(&self, other: &String) -> bool {
self.as_str() == other.as_str()
}
}
impl<const N: usize, const M: usize> PartialOrd<SizedStr<M>> for SizedStr<N> {
#[inline(always)]
fn partial_cmp(&self, other: &SizedStr<M>) -> Option<Ordering> {
self.as_str().partial_cmp(other.as_str())
}
}
impl<const N: usize> PartialOrd<&str> for SizedStr<N> {
#[inline(always)]
fn partial_cmp(&self, other: &&str) -> Option<Ordering> {
self.as_str().partial_cmp(*other)
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<const N: usize> PartialOrd<String> for SizedStr<N> {
#[inline(always)]
fn partial_cmp(&self, other: &String) -> Option<Ordering> {
self.as_str().partial_cmp(other.as_str())
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
#[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<const N: usize>(SizedSlice<u8, N>);
impl<const N: usize> SizedStr<N> {
/// 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<Self, StringError> {
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<const N: usize> SizedStr<N> {
/// Each byte value must be a valid UTF-8 code point.
#[inline]
pub const fn from_utf8(data: &[u8]) -> Result<Self, StringError> {
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<const N: usize> SizedStr<N> {
}
};
// 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<const N: usize> SizedStr<N> {
#[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<const N: usize> SizedStr<N> {
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<u8>; N]>().read() };
let buf = unsafe { buf.as_ptr().cast::<[MaybeUninit<u8>; N]>().read() };
Self(SizedSlice::from_raw_parts(buf, len))
}
@ -231,6 +153,8 @@ impl<const N: usize> SizedStr<N> {
#[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<const N: usize> SizedStr<N> {
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<const N: usize> SizedStr<N> {
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<const N: usize> BorrowMut<str> for SizedStr<N> {
}
}
impl<const N: usize> Debug for SizedStr<N> {
#[inline]
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Debug::fmt(self.as_str(), f)
}
}
impl<const N: usize> Deref for SizedStr<N> {
type Target = str;
@ -417,162 +290,18 @@ impl<const N: usize> DerefMut for SizedStr<N> {
}
}
impl<const N: usize> Decode for SizedStr<N> {
#[inline]
fn decode(stream: &mut IStream) -> Result<Self, DecodeError> {
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<const N: usize> Display for SizedStr<N> {
#[inline]
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Display::fmt(self.as_str(), f)
}
}
impl<const N: usize> Encode for SizedStr<N> {
#[inline(always)]
fn encode(&self, stream: &mut OStream) -> Result<(), EncodeError> {
// Optimised encode. Don't just rely on `SizedSlice`.
self.as_str().encode(stream)
}
}
impl<const N: usize> Eq for SizedStr<N> { }
impl<const N: usize> FromIterator<char> for SizedStr<N> {
#[inline]
fn from_iter<I: IntoIterator<Item = char>>(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<const N: usize> FromStr for SizedStr<N> {
type Err = StringError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
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<const N: usize> Hash for SizedStr<N> {
#[inline(always)]
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_str().hash(state)
}
}
impl<I: SliceIndex<str>, const N: usize> Index<I> for SizedStr<N> {
type Output = I::Output;
#[inline(always)]
fn index(&self, index: I) -> &Self::Output {
self.get(index).unwrap()
}
}
impl<I: SliceIndex<str>, const N: usize> IndexMut<I> for SizedStr<N> {
#[inline(always)]
fn index_mut(&mut self, index: I) -> &mut Self::Output {
self.get_mut(index).unwrap()
}
}
impl<const N: usize> Ord for SizedStr<N> {
#[inline(always)]
fn cmp(&self, other: &Self) -> Ordering {
self.as_str().cmp(other.as_str())
}
}
impl<const N: usize, const M: usize> PartialEq<SizedStr<M>> for SizedStr<N> {
#[inline(always)]
fn eq(&self, other: &SizedStr<M>) -> bool {
self.as_str() == other.as_str()
}
}
impl<const N: usize> PartialEq<&str> for SizedStr<N> {
#[inline(always)]
fn eq(&self, other: &&str) -> bool {
self.as_str() == *other
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<const N: usize> PartialEq<String> for SizedStr<N> {
#[inline(always)]
fn eq(&self, other: &String) -> bool {
self.as_str() == other.as_str()
}
}
impl<const N: usize, const M: usize> PartialOrd<SizedStr<M>> for SizedStr<N> {
#[inline(always)]
fn partial_cmp(&self, other: &SizedStr<M>) -> Option<Ordering> {
self.as_str().partial_cmp(other.as_str())
}
}
impl<const N: usize> PartialOrd<&str> for SizedStr<N> {
#[inline(always)]
fn partial_cmp(&self, other: &&str) -> Option<Ordering> {
self.as_str().partial_cmp(*other)
}
}
#[cfg(feature = "alloc")]
#[cfg_attr(doc, doc(cfg(feature = "alloc")))]
impl<const N: usize> PartialOrd<String> for SizedStr<N> {
#[inline(always)]
fn partial_cmp(&self, other: &String) -> Option<Ordering> {
self.as_str().partial_cmp(other.as_str())
}
}
unsafe impl<const N: usize> SizedEncode for SizedStr<N> {
const MAX_ENCODED_SIZE: usize =
usize::MAX_ENCODED_SIZE
+ SizedSlice::<u8, N>::MAX_ENCODED_SIZE;
}
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
impl<const N: usize> ToSocketAddrs for SizedStr<N> {

204
librum/src/sized_str/mod.rs Normal file
View file

@ -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 <https://www.gnu.org/licenses/>.
#[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<const N: usize>(SizedSlice<u8, N>);
impl<const N: usize> SizedStr<N> {
/// 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<Self, StringError> {
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<const N: usize> Debug for SizedStr<N> {
#[inline(always)]
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Debug::fmt(self.as_str(), f)
}
}
impl<const N: usize> Display for SizedStr<N> {
#[inline(always)]
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
Display::fmt(self.as_str(), f)
}
}
impl<const N: usize> Eq for SizedStr<N> { }
impl<const N: usize> FromIterator<char> for SizedStr<N> {
#[inline]
fn from_iter<I: IntoIterator<Item = char>>(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<const N: usize> Hash for SizedStr<N> {
#[inline(always)]
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_str().hash(state)
}
}
impl<I: SliceIndex<str>, const N: usize> Index<I> for SizedStr<N> {
type Output = I::Output;
#[inline(always)]
fn index(&self, index: I) -> &Self::Output {
self.get(index).unwrap()
}
}
impl<I: SliceIndex<str>, const N: usize> IndexMut<I> for SizedStr<N> {
#[inline(always)]
fn index_mut(&mut self, index: I) -> &mut Self::Output {
self.get_mut(index).unwrap()
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
use crate::{
Decode,
DecodeBorrowed,
Encode,
IStream,
OStream,
SizedEncode,
SizedStr
};
use crate::error::{CollectionDecodeError, SizeError, StringError, Utf8Error};
impl<const N: usize> Decode for SizedStr<N> {
type Error = CollectionDecodeError<SizeError, Utf8Error>;
#[inline]
fn decode(stream: &mut IStream) -> Result<Self, Self::Error> {
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<const N: usize> DecodeBorrowed<str> for SizedStr<N> { }
impl<const N: usize> Encode for SizedStr<N> {
type Error = <str as Encode>::Error;
#[inline(always)]
fn encode(&self, stream: &mut OStream) -> Result<(), Self::Error> {
self.as_str().encode(stream)
}
}
impl<const N: usize> SizedEncode for SizedStr<N> {
const MAX_ENCODED_SIZE: usize =
usize::MAX_ENCODED_SIZE
+ u8::MAX_ENCODED_SIZE * N;
}

View file

@ -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 <https://www.gnu.org/licenses/>.
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() {