Improved dcb-bytes's documentation and added a derive.

This commit is contained in:
Filipe Rodrigues 2021-01-24 10:06:49 +00:00
parent baf40599a1
commit 792b1d365c
5 changed files with 181 additions and 46 deletions

View File

@ -6,3 +6,6 @@ edition = "2018"
[dependencies]
# Bytes
byteorder = "1.4.2"
arrayref = "0.3.6"

View File

@ -0,0 +1,82 @@
//! [`ByteOrderExt`] helper
// Imports
use crate::ByteArray;
use byteorder::ByteOrder;
/// Helper trait for [`ByteOrder`] to use a generic type
pub trait ByteOrderExt<B: ByteOrder>: Sized {
/// Array type
type ByteArray: ByteArray;
/// Reads this type
fn read(bytes: &Self::ByteArray) -> Self;
/// Writes this type
fn write(&self, bytes: &mut Self::ByteArray);
}
impl<B: ByteOrder, const N: usize> ByteOrderExt<B> for [u8; N] {
type ByteArray = [u8; N];
fn read(bytes: &Self::ByteArray) -> Self {
*bytes
}
fn write(&self, bytes: &mut Self::ByteArray) {
*bytes = *self;
}
}
#[allow(clippy::use_self)] // We want the byte buffer to be `[u8; _]`
impl<B: ByteOrder> ByteOrderExt<B> for u8 {
type ByteArray = [u8; 1];
fn read(bytes: &Self::ByteArray) -> Self {
bytes[0]
}
fn write(&self, bytes: &mut Self::ByteArray) {
bytes[0] = *self;
}
}
#[allow(clippy::as_conversions, clippy::cast_possible_wrap, clippy::cast_sign_loss)] // We want to explicitly convert it from bytes
impl<B: ByteOrder> ByteOrderExt<B> for i8 {
type ByteArray = [u8; 1];
fn read(bytes: &Self::ByteArray) -> Self {
bytes[0] as Self
}
fn write(&self, bytes: &mut Self::ByteArray) {
bytes[0] = *self as u8;
}
}
macro_rules! impl_read_bytes {
($($T:ty, $SIZE:literal, $read_func:ident, $write_func:ident),* $(,)?) => {
$(
impl<B: ByteOrder> ByteOrderExt<B> for $T {
type ByteArray = [u8; $SIZE];
fn read(bytes: &Self::ByteArray) -> Self {
B::$read_func(bytes)
}
fn write(&self, bytes: &mut Self::ByteArray) {
B::$write_func(bytes, *self);
}
}
)*
};
}
impl_read_bytes! {
u16, 2, read_u16, write_u16,
u32, 4, read_u32, write_u32,
u64, 8, read_u64, write_u64,
i16, 2, read_i16, write_i16,
i32, 4, read_i32, write_i32,
i64, 8, read_i64, write_i64,
}

View File

@ -1,9 +1,9 @@
//! Interface for converting various structures to and from bytes
//! `Bytes` trait.
// Imports
use std::error::Error;
/// Conversions to and from bytes for the game file
/// Conversion from and to bytes
pub trait Bytes
where
Self: Sized,
@ -25,10 +25,15 @@ where
}
/// A trait for restricting `Bytes::ByteArray`
pub trait ByteArray {}
pub trait ByteArray {
/// Array size
const SIZE: usize;
}
impl<const N: usize> ByteArray for [u8; N] {}
impl<const N: usize> ByteArray for [u8; N] {
const SIZE: usize = N;
}
impl ByteArray for [u8] {}
impl ByteArray for u8 {}
impl ByteArray for u8 {
const SIZE: usize = 1;
}

42
dcb-bytes/src/derive.rs Normal file
View File

@ -0,0 +1,42 @@
//! Derives for [`Bytes`](super::Bytes)
/// Derives `Bytes` by splitting the input bytes and parsing them with `BYTEORDER`
#[macro_export]
macro_rules! derive_bytes_split {
($T:ty, $BYTEORDER:ident, $($field:ident : $U:ty),* $(,)?) => {
const _: () = {
use $crate::ByteOrderExt;
use $crate::byteorder::$BYTEORDER as Order;
use $crate::ByteArray;
use $crate::arrayref;
#[allow(clippy::ptr_offset_with_cast)] // `arrayref` does it
impl $crate::Bytes for $T {
type ByteArray = [u8; {0 $( + <<$U as ByteOrderExt<Order>>::ByteArray as ByteArray>::SIZE )*}];
type FromError = !;
type ToError = !;
fn from_bytes(bytes: &Self::ByteArray) -> Result<Self, Self::FromError> {
let ( $($field,)* ) = arrayref::array_refs![bytes, $( <<$U as ByteOrderExt<Order>>::ByteArray as ByteArray>::SIZE ),*];
Ok(Self {
$(
$field: <$U as ByteOrderExt::<Order>>::read( $field ),
)*
})
}
fn to_bytes(&self, bytes: &mut Self::ByteArray) -> Result<(), Self::ToError> {
let ( $($field,)* ) = arrayref::mut_array_refs![bytes, $( <<$U as ByteOrderExt<Order>>::ByteArray as ByteArray>::SIZE ),*];
$(
<$U as ByteOrderExt::<Order>>::write(&self.$field, $field);
)*
Ok(())
}
}
};
};
}

View File

@ -1,57 +1,60 @@
//! Bytes conversions for game types.
//! Byte conversions
// Features
#![feature(unsafe_block_in_unsafe_fn, min_const_generics)]
#![feature(min_const_generics, never_type)]
// Lints
#![warn(clippy::restriction, clippy::pedantic, clippy::nursery)]
// Instead of `unwrap`, we must use `expect` and provide a reason
#![forbid(clippy::unwrap_used)]
// We must use `unsafe` in unsafe `fn`s and specify if the guarantee is
// made by the caller or by us.
#![forbid(unsafe_op_in_unsafe_fn)]
// We'll disable the ones we don't need
#![allow(clippy::blanket_clippy_restriction_lints)]
// Necessary items may be inlined using `LTO`, so we don't need to mark them as inline
// No unsafe allowed in this crate
#![forbid(unsafe_code)]
// Must use `expect` instead of `unwrap`
#![forbid(clippy::unwrap_used)]
// We don't need to mark every public function `inline`
#![allow(clippy::missing_inline_in_public_items)]
// We prefer tail returns where possible, as they help with code readability in most cases.
// We prefer literals to be copy-paste-able rather than readable
#![allow(clippy::unreadable_literal)]
// We prefer suffixes to be glued to the literal
#![allow(clippy::unseparated_literal_suffix)]
// We're fine with panicking when entering an unexpected state
#![allow(
clippy::panic,
clippy::unreachable,
clippy::expect_used,
clippy::panic_in_result_fn,
clippy::unwrap_in_result,
clippy::indexing_slicing,
clippy::todo
)]
// We prefer tail calls
#![allow(clippy::implicit_return)]
// We're fine with shadowing, as long as the variable is used for the same purpose.
// Hence why `clippy::shadow_unrelated` isn't allowed.
#![allow(clippy::shadow_reuse, clippy::shadow_same)]
// We panic when we know it won't happen, or if it does happen, then a panic is the best option
#![allow(clippy::panic, clippy::expect_used, clippy::unreachable, clippy::todo)]
// We use `expect` even in functions that return a `Result` / `Option` if there is a logic error
#![allow(clippy::unwrap_in_result)]
// We find it more important to be able to copy paste literals such as `0xabcd1234` than
// being able to read them, which does not provide many benefits
#![allow(clippy::unreadable_literal, clippy::unseparated_literal_suffix)]
// We separate implementations per their functionality usually, such as constructors, getters, setters, and others.
// We use multiple implementations to separate logic
#![allow(clippy::multiple_inherent_impl)]
// Many operations we need to repeat, and to keep symmetry
#![allow(clippy::identity_op)]
// We only introduce items before their first usage, which sometimes is half-way through the code.
// We make sure that we only use the item after introduced, however.
#![allow(clippy::items_after_statements)]
// Useful for when they either change a lot with new variants / data,
// or for symmetry purposes
#![allow(clippy::match_same_arms)]
// In this library we have very grain-level error types, each function
// will have it's own error type ideally, so any errors are explicit
// by the type, without needing a section for them
// We use granular error types, usually one for each function, which document the
// errors that might happen, as opposed to documenting them in the function
#![allow(clippy::missing_errors_doc)]
// Although we generally try to avoid this, this can happen due to our module organization.
// In the future, this lint should be removed globally and only enabled for modules which
// actually require the use of it.
#![allow(clippy::module_inception, clippy::module_name_repetitions)]
// We use integer arithmetic and operations with the correct intent
// Due to our module organization, we end up with data types inheriting their module's name
#![allow(clippy::module_name_repetitions)]
// We need arithmetic for this crate
#![allow(clippy::integer_arithmetic, clippy::integer_division)]
// We prefer using match ergonomic where possible
// We want to benefit from match ergonomics where possible
#![allow(clippy::pattern_type_mismatch)]
// Sometimes the blocks make it easier to invert their order
#![allow(clippy::if_not_else)]
// We only use wildcards when we only care about certain variants
#![allow(clippy::wildcard_enum_match_arm, clippy::match_wildcard_for_single_variants)]
// We're fine with shadowing, as long as it's related
#![allow(clippy::shadow_reuse, clippy::shadow_same)]
// Matching on booleans can look better than `if / else`
#![allow(clippy::match_bool)]
// If the `else` isn't needed, we don't put it
#![allow(clippy::else_if_without_else)]
// Modules
pub mod byteorder_ext;
pub mod bytes;
pub mod derive;
// Exports
pub use byteorder_ext::ByteOrderExt;
pub use bytes::{ByteArray, Bytes};
#[doc(hidden)]
pub use ::{arrayref, byteorder};