feat: auto impl From and TryFrom

This commit is contained in:
Kristofers Solo 2025-10-04 18:41:46 +03:00
parent 15481c0a9a
commit 13e518c4e5
Signed by: kristoferssolo
GPG Key ID: 8687F2D3EEE6F0ED
7 changed files with 181 additions and 120 deletions

View File

@ -1,8 +1,9 @@
use crate::grammar; use crate::grammar;
use unsynn::*; use quote::format_ident;
use unsynn::Ident;
pub struct Struct { pub struct Struct {
pub bit_width: u128, pub bit_width: u8,
pub error_type: Ident, pub error_type: Ident,
pub name: Ident, pub name: Ident,
pub body: Ident, pub body: Ident,
@ -10,9 +11,11 @@ pub struct Struct {
impl From<grammar::StructDef> for Struct { impl From<grammar::StructDef> for Struct {
fn from(value: grammar::StructDef) -> Self { fn from(value: grammar::StructDef) -> Self {
let bit_width = u8::try_from(value.bit_width.bit_width.content.width.content.value())
.expect("8-bit value");
Self { Self {
bit_width: value.bit_width.bit_width.content.width.content.value(), bit_width,
error_type: value.error_type.error.content.error.content, error_type: format_ident!("{}Error", value.name),
name: value.name, name: value.name,
body: value.body.content, body: value.body.content,
} }

View File

@ -1,5 +1,5 @@
use crate::{codegen::generate_impl, grammar::StructDef}; use crate::{codegen::generate_impl, grammar::StructDef};
use unsynn::*; use unsynn::{IParse, ToTokens, TokenStream};
pub fn impl_bit_wrapper(input: &TokenStream) -> TokenStream { pub fn impl_bit_wrapper(input: &TokenStream) -> TokenStream {
let parsed = input let parsed = input

View File

@ -1,8 +1,8 @@
use crate::ast::Struct; use crate::ast::Struct;
use quote::quote; use quote::quote;
use unsynn::*; use unsynn::{Ident, TokenStream};
#[ignore = "too_many_lines"] #[allow(clippy::too_many_lines)]
pub fn generate_impl(info: &Struct) -> TokenStream { pub fn generate_impl(info: &Struct) -> TokenStream {
let name = &info.name; let name = &info.name;
let inner = &info.body; let inner = &info.body;
@ -10,8 +10,12 @@ pub fn generate_impl(info: &Struct) -> TokenStream {
let ops = generate_bitwise_ops(info); let ops = generate_bitwise_ops(info);
let fmt = generate_bitwise_fmt(info); let fmt = generate_bitwise_fmt(info);
let wrapper = generate_bitwise_wrapper(info); let wrapper = generate_bitwise_wrapper(info);
let error = generate_error(info);
let conversions = generate_conversions(info);
quote! { quote! {
#error
#conversions
#ops #ops
#fmt #fmt
#wrapper #wrapper
@ -37,6 +41,91 @@ pub fn generate_impl(info: &Struct) -> TokenStream {
} }
} }
fn generate_conversions(info: &Struct) -> TokenStream {
let name = &info.name;
let inner = &info.body;
let error_type = &info.error_type;
let bit_width = info.bit_width;
// Generate From implementations for smaller integer types (guaranteed safe)
let impl_from = |target_width: u8, srouce_type: &TokenStream| {
generate_from_impl(bit_width, target_width, srouce_type, name, inner)
};
let from_u8 = impl_from(8, &quote! { u8 });
let from_u16 = impl_from(15, &quote! { u16 });
let from_u32 = impl_from(32, &quote! { u32 });
let from_u64 = impl_from(64, &quote! { u64 });
let impl_try_from = |target_width: u8, srouce_type: &TokenStream| {
generate_try_from_impl(
bit_width,
target_width,
srouce_type,
name,
inner,
error_type,
)
};
let try_from_u8 = impl_try_from(8, &quote! { u8 });
let try_from_u16 = impl_try_from(16, &quote! { u16 });
let try_from_u32 = impl_try_from(32, &quote! { u32 });
let try_from_u64 = impl_try_from(64, &quote! { u64 });
quote! {
#from_u8
#from_u16
#from_u32
#from_u64
#try_from_u8
#try_from_u16
#try_from_u32
#try_from_u64
}
}
fn generate_error(info: &Struct) -> TokenStream {
let error_type = &info.error_type;
let name = &info.name;
let inner = &info.body;
quote! {
#[derive(Debug, thiserror::Error)]
pub enum #error_type {
#[error("#name must be a {width}-bit number (0 to {max}), got {value}")]
ValueOutOfRange {
value: #inner,
max: #inner,
width: u8
},
#[error("Hex value 0x{value:X} exceeds {bit_width}-bit limit (masked to 0x{masked:0width$X})")]
ExceedsBitLimit {
value: u64,
bit_width: u8,
masked: u64,
width: usize,
},
#[error("Failed to parse subkey value: {0}")]
ParseError(#[from] std::num::ParseIntError),
#[error("Unknown error: {0}")]
Unknown(String),
}
impl #error_type {
pub fn value_out_of_range(value: #inner) -> Self {
Self::ValueOutOfRange {
value,
max: #name::MAX,
width: #name::BIT_WIDTH,
}
}
}
}
}
fn generate_bitwise_fmt(info: &Struct) -> TokenStream { fn generate_bitwise_fmt(info: &Struct) -> TokenStream {
let name = &info.name; let name = &info.name;
@ -53,13 +142,13 @@ fn generate_bitwise_fmt(info: &Struct) -> TokenStream {
} }
} }
impl std::fmt::Debug for Subkey { impl std::fmt::Debug for #name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Subkey(0x{:012X})", self.0) write!(f, "#name(0x{:012X})", self.0)
} }
} }
impl std::fmt::Display for Subkey { impl std::fmt::Display for #name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "0x{:012X}", self.0) write!(f, "0x{:012X}", self.0)
} }
@ -67,16 +156,22 @@ fn generate_bitwise_fmt(info: &Struct) -> TokenStream {
} }
} }
#[allow(clippy::similar_names)]
#[allow(clippy::too_many_lines)]
fn generate_bitwise_ops(info: &Struct) -> TokenStream { fn generate_bitwise_ops(info: &Struct) -> TokenStream {
let name = &info.name; let name = &info.name;
let inner = &info.body; let inner = &info.body;
let error_type = &info.error_type; let error_type = &info.error_type;
let bit_width = u8::try_from(info.bit_width).expect("8-bit value"); let bit_width = info.bit_width;
let hex_width = usize::from(bit_width.div_ceil(4)); let hex_width = usize::from(bit_width.div_ceil(4));
let bin_width = usize::from(bit_width); let bin_width = usize::from(bit_width);
let max_value = (1u64 << (bit_width)) - 1; let max_value = if bit_width == 64 {
u64::MAX
} else {
(1u64 << (bit_width)) - 1
};
quote! { quote! {
impl #name { impl #name {
@ -316,117 +411,162 @@ fn generate_bitwise_ops(info: &Struct) -> TokenStream {
} }
} }
fn generate_bitwise_wrapper(_info: &Struct) -> TokenStream { fn generate_bitwise_wrapper(info: &Struct) -> TokenStream {
let name = &info.name;
quote! { quote! {
impl std::ops::Not for Subkey { impl std::ops::Not for #name {
type Output = Self; type Output = Self;
fn not(self) -> Self::Output { fn not(self) -> Self::Output {
Self(!self.0) Self(!self.0)
} }
} }
impl std::ops::BitAnd for Subkey { impl std::ops::BitAnd for #name {
type Output = Self; type Output = Self;
fn bitand(self, rhs: Self) -> Self::Output { fn bitand(self, rhs: Self) -> Self::Output {
Self(self.0 & rhs.0) Self(self.0 & rhs.0)
} }
} }
impl std::ops::BitAnd<&Self> for Subkey { impl std::ops::BitAnd<&Self> for #name {
type Output = Self; type Output = Self;
fn bitand(self, rhs: &Self) -> Self::Output { fn bitand(self, rhs: &Self) -> Self::Output {
Self(self.0 & rhs.0) Self(self.0 & rhs.0)
} }
} }
impl std::ops::BitAndAssign for Subkey { impl std::ops::BitAndAssign for #name {
fn bitand_assign(&mut self, rhs: Self) { fn bitand_assign(&mut self, rhs: Self) {
self.0 &= rhs.0; self.0 &= rhs.0;
} }
} }
impl std::ops::BitAndAssign<&Self> for Subkey { impl std::ops::BitAndAssign<&Self> for #name {
fn bitand_assign(&mut self, rhs: &Self) { fn bitand_assign(&mut self, rhs: &Self) {
self.0 &= rhs.0; self.0 &= rhs.0;
} }
} }
impl std::ops::BitOr for Subkey { impl std::ops::BitOr for #name {
type Output = Self; type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output { fn bitor(self, rhs: Self) -> Self::Output {
Self(self.0 | rhs.0) Self(self.0 | rhs.0)
} }
} }
impl std::ops::BitOr<&Self> for Subkey { impl std::ops::BitOr<&Self> for #name {
type Output = Self; type Output = Self;
fn bitor(self, rhs: &Self) -> Self::Output { fn bitor(self, rhs: &Self) -> Self::Output {
Self(self.0 | rhs.0) Self(self.0 | rhs.0)
} }
} }
impl std::ops::BitOrAssign for Subkey { impl std::ops::BitOrAssign for #name {
fn bitor_assign(&mut self, rhs: Self) { fn bitor_assign(&mut self, rhs: Self) {
self.0 |= rhs.0; self.0 |= rhs.0;
} }
} }
impl std::ops::BitOrAssign<&Self> for Subkey { impl std::ops::BitOrAssign<&Self> for #name {
fn bitor_assign(&mut self, rhs: &Self) { fn bitor_assign(&mut self, rhs: &Self) {
self.0 |= rhs.0; self.0 |= rhs.0;
} }
} }
impl std::ops::BitXor for Subkey { impl std::ops::BitXor for #name {
type Output = Self; type Output = Self;
fn bitxor(self, rhs: Self) -> Self { fn bitxor(self, rhs: Self) -> Self {
Self(self.0 ^ rhs.0) Self(self.0 ^ rhs.0)
} }
} }
impl std::ops::BitXor<&Self> for Subkey { impl std::ops::BitXor<&Self> for #name {
type Output = Self; type Output = Self;
fn bitxor(self, rhs: &Self) -> Self { fn bitxor(self, rhs: &Self) -> Self {
Self(self.0 ^ rhs.0) Self(self.0 ^ rhs.0)
} }
} }
impl std::ops::BitXorAssign for Subkey { impl std::ops::BitXorAssign for #name {
fn bitxor_assign(&mut self, rhs: Self) { fn bitxor_assign(&mut self, rhs: Self) {
self.0 ^= rhs.0; self.0 ^= rhs.0;
} }
} }
impl std::ops::BitXorAssign<&Self> for Subkey { impl std::ops::BitXorAssign<&Self> for #name {
fn bitxor_assign(&mut self, rhs: &Self) { fn bitxor_assign(&mut self, rhs: &Self) {
self.0 ^= rhs.0; self.0 ^= rhs.0;
} }
} }
impl std::ops::Shl<u32> for Subkey { impl std::ops::Shl<u32> for #name {
type Output = Self; type Output = Self;
fn shl(self, rhs: u32) -> Self { fn shl(self, rhs: u32) -> Self {
Self(self.0 << rhs) Self(self.0 << rhs)
} }
} }
impl std::ops::ShlAssign<u32> for Subkey { impl std::ops::ShlAssign<u32> for #name {
fn shl_assign(&mut self, rhs: u32) { fn shl_assign(&mut self, rhs: u32) {
self.0 <<= rhs; self.0 <<= rhs;
} }
} }
impl std::ops::Shr<u32> for Subkey { impl std::ops::Shr<u32> for #name {
type Output = Self; type Output = Self;
fn shr(self, rhs: u32) -> Self::Output { fn shr(self, rhs: u32) -> Self::Output {
Self(self.0 >> rhs) Self(self.0 >> rhs)
} }
} }
impl std::ops::ShrAssign<u32> for Subkey { impl std::ops::ShrAssign<u32> for #name {
fn shr_assign(&mut self, rhs: u32) { fn shr_assign(&mut self, rhs: u32) {
self.0 >>= rhs; self.0 >>= rhs;
} }
} }
} }
} }
fn generate_from_impl(
bit_width: u8,
target_width: u8,
source_type: &TokenStream,
target_type: &Ident,
inner_type: &Ident,
) -> Option<TokenStream> {
if bit_width < target_width {
return None;
}
Some(quote! {
impl std::convert::From<#source_type> for #target_type {
fn from(key: #source_type) -> Self {
Self(key as #inner_type)
}
}
})
}
fn generate_try_from_impl(
bit_width: u8,
target_width: u8,
source_type: &TokenStream,
target_type: &Ident,
inner_type: &Ident,
error_type: &Ident,
) -> Option<TokenStream> {
if bit_width >= target_width {
return None;
}
Some(quote! {
impl std::convert::TryFrom<#source_type> for #target_type {
type Error = #error_type;
fn try_from(key: #source_type) -> Result<Self, Self::Error> {
if key > #target_type::MAX {
return Err(#error_type::value_out_of_range(key));
}
Ok(Self(key as #inner_type))
}
}
})
}

View File

@ -1,10 +1,12 @@
use unsynn::*; use unsynn::{
BracketGroupContaining, Ident, LiteralInteger, ParenthesisGroupContaining, Pound, Semicolon,
TokenIter, keyword, unsynn,
};
keyword! { keyword! {
pub KwStruct = "struct"; pub KwStruct = "struct";
pub KwPub = "pub"; pub KwPub = "pub";
pub KwBitWidth = "bit_width"; pub KwBitWidth = "bit_width";
pub KwError = "bit_conversion_error";
} }
unsynn! { unsynn! {
@ -18,19 +20,8 @@ unsynn! {
pub bit_width: BracketGroupContaining<BitWidth>, pub bit_width: BracketGroupContaining<BitWidth>,
} }
pub struct ErrorType {
pub kw_bit_width: KwError,
pub error: ParenthesisGroupContaining<Ident>,
}
pub struct AttributeError {
pub pound: Pound,
pub error: BracketGroupContaining<ErrorType>,
}
pub struct StructDef { pub struct StructDef {
pub bit_width: Attribute, pub bit_width: Attribute,
pub error_type: AttributeError,
pub vis: Option<KwPub>, pub vis: Option<KwPub>,
pub kw_struct: KwStruct, pub kw_struct: KwStruct,
pub name: Ident, pub name: Ident,
@ -38,24 +29,3 @@ unsynn! {
pub semi: Semicolon, pub semi: Semicolon,
} }
} }
#[cfg(test)]
mod tests {
use super::*;
const SAMPLE: &str = r#"
#[bit_width(48)]
#[bit_conversion_error()]
pub struct Subkey(u64);
"#;
#[test]
fn parse_attribute() {
let mut iter = SAMPLE.to_token_iter();
let sdef = iter
.parse::<StructDef>()
.expect("failed to parse StructDef");
dbg!(sdef);
assert!(false);
}
}

View File

@ -6,7 +6,7 @@ mod grammar;
use crate::bit_wrapper::impl_bit_wrapper; use crate::bit_wrapper::impl_bit_wrapper;
use proc_macro::TokenStream; use proc_macro::TokenStream;
#[proc_macro_derive(BitWrapper, attributes(bit_width, bit_conversion_error))] #[proc_macro_derive(BitWrapper, attributes(bit_width))]
pub fn derive_bit_wrapper(input: TokenStream) -> TokenStream { pub fn derive_bit_wrapper(input: TokenStream) -> TokenStream {
impl_bit_wrapper(&input.into()).into() impl_bit_wrapper(&input.into()).into()
} }

View File

@ -1,29 +1,5 @@
use std::fmt::{Debug, Display}; use bit_wrap::BitWrapper;
#[derive(Clone, Copy, PartialEq, Eq)] #[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, BitWrapper)]
#[bit_width(64)]
pub struct Key(u64); pub struct Key(u64);
impl Key {
// #[macro_use]
// pub fn new(key: u64) -> Self {
// key.into()
// }
}
impl From<u64> for Key {
fn from(key: u64) -> Self {
Self(key)
}
}
impl Debug for Key {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Key(0x{:016X})", self.0)
}
}
impl Display for Key {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "0x{:016X}", self.0)
}
}

View File

@ -3,31 +3,3 @@ use bit_wrap::BitWrapper;
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, BitWrapper)] #[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, BitWrapper)]
#[bit_width(48)] #[bit_width(48)]
pub struct Subkey(u64); pub struct Subkey(u64);
// impl TryFrom<u64> for Subkey {
// type Error = SubkeyError;
// fn try_from(key: u64) -> Result<Self, Self::Error> {
// if key > Self::MAX {
// return Err(SubkeyError::ValueOutOfRange(key));
// }
// Ok(Self(key))
// }
// }
//
// impl From<u32> for Subkey {
// fn from(value: u32) -> Self {
// Self(u64::from(value))
// }
// }
//
// impl From<u16> for Subkey {
// fn from(value: u16) -> Self {
// Self(u64::from(value))
// }
// }
//
// impl From<u8> for Subkey {
// fn from(value: u8) -> Self {
// Self(u64::from(value))
// }
// }